Skip to content
wannacode

Selecting: [], loc and iloc

In short: In pandas [] selects columns, loc selects rows and cells by label, and iloc selects them by position.

Problem

“The cheese is going up”

The supplier raises the price of white cheese from €7.90 to €8.40. The manager wants three things:

  • a small table for the counter with only the name and the price;
  • the price and stock of one product, without searching by eye;
  • the new price in the table, not in some copy off to the side.

This is selecting: taking a part of the table, columns, rows or one cell, and sometimes changing it. pandas has three tools for it: [], loc and iloc.

The idea

Columns: []

You already take one column with []. If you put a list of names in the brackets, you get a table with those columns, in that order:

Pythondata: the shop tables

One name gives a Series with shape (25,). A list gives a DataFrame with shape (25, 2), even if the list has one column: products[['price']] is a table. The double brackets are not special syntax: the outer ones are for selecting, and the inner ones are for the list.

The idea

By label: loc

loc selects by labels: first the rows, then the columns, separated by a comma. With the names in the index this is “row White cheese, column price”:

Pythondata: the shop tables
  • A row and a column give one cell: 7.9.
  • A row alone gives the whole row as a Series. Its labels are now the names of the columns.
  • Lists give a table: the cheese and the butter with price and stock.

See it

A slice with loc includes the end

A slice with a colon (start:stop) works in loc too, but there is a difference that confuses everyone:

Pythondata: the shop tables

loc[0:3] gives 4 names: from label 0 to label 3 inclusive. A slice of positions (iloc, Python lists) stops before the end and gives 3.

The reason is the labels. With names in the index, loc['Fresh milk':'Butter'] gives the dairy products, from the milk to the butter. Nobody would want the butter left out just because it is written last. How would you write “up to just after the butter”?

The idea

By position: iloc

iloc selects by position, like a NumPy array: the first row is 0, the last is -1. The labels play no part:

Pythondata: the shop tables

iloc[0, 1] is the first row, second column: “White bread”. iloc[-1, 1] is the last product: the gift card. iloc[:3, [1, 3]] is the first three rows, the columns in places 1 and 3.

Which one when? loc when you know what you are looking for: a product, a column by name. iloc when you care about the place: the first 5, the last row. Positions move as soon as the table is sorted or cut, while labels stay.

1. The index is the default one (0, 1, 2…). How many rows do products.loc[2:5] and products.iloc[2:5] give?

Try it

Coffee in the stockroom

Coffee is the product with id 17. How many packs are in stock? Put id in the index (by_id) and store the stock in coffee_stock as one number.

Pythondata: the shop tables

The idea

A copy or the same table

Now the new price. You cut out the dairy products and change the price of the cheese in them. Does products change?

Pythondata: the shop tables

No: in dairy the cheese is 8.4, but in products it is still 7.9. In pandas 3 every cut-out piece behaves as a copy. Changing the piece does not touch the original. This is called Copy-on-Write: pandas does not copy the data straight away, only when one of the two objects changes.

To change the table, you write into the table itself, with loc and the row and column at once: products.loc[6, 'price'] = 8.4.

See it

Two brackets in a row do not write

The temptation is products['price'][6] = 8.4: the column, then the cell. It looks like loc, but it is not:

Pythondata: the shop tables

The price is still 7.9. products['price'] is a separate object, a copy, and [6] = 8.4 writes into it. This is called chained assignment. pandas 3 never applies it to the table and shows a ChainedAssignmentError warning with a hint about .loc.

There is one rule: you write with one loc[row, column] = … on the table you want to change.

More precisely, if you are curious

In pandas 1 and 2 the same code sometimes changed the table and sometimes did not, depending on whether there was a copy or a view underneath. That is why there was a SettingWithCopyWarning, which said “maybe”. You will meet it in old articles and in answers around the internet. In pandas 3 (2026) the rule is clear, and the warning is history. If your code has to run on an old version too, loc in one step is correct there as well.

The idea

Many rows at once: a mask in loc

In the row place of loc you can put a mask: a column of True and False, like the masks in NumPy. Then the change applies to every row where the mask is True. Two products are out of stock and a delivery of 30 each arrives:

Pythondata: the shop tables

empty.sum() counts the empty ones: 2. After the loc line the banitsa and the cucumbers have 30 each. We will use masks on a table a lot in the next lesson, on filters.

Try it

A delivery for the nearly empty

Every product with fewer than 10 in stock gets 20 more. Store the mask in low, then change their stock in products.

Pythondata: the shop tables

Exercise

The table for the counter

The names are in the index of by_name. Change the price of White cheese to 8.4 in by_name. Then store in counter the table for the counter: the dairy products from Fresh milk to Butter, with the columns price and stock.

Pythondata: the shop tables

Test

Check yourself

1. Which one returns a table (DataFrame) with the columns name and price?
2. The index is the product names. What does by_name.loc['White cheese', 'price'] give?
3. products.loc[17, 'stock'] gives 33, but coffee (id 17) has 15 packs. Why?
4. In pandas 3: short = products.loc[low], then short['stock'] = 30. What happens to products?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·