Selecting rows and columns with loc and iloc in pandas extracts a precise subset from a DataFrame before analysis, cleanup, joining, or export. The two indexers make the selection rule explicit, so label-based row IDs and positional table coordinates do not get mixed.
Use loc when row labels, column labels, or a boolean condition identify the subset. Label slices include both the start and stop labels, and a boolean Series aligns by index before masking.
Use iloc when integer positions identify the subset independently of labels. Position slices use normal Python slice boundaries, so the stop position is excluded; check the resulting shape, labels, and key values before feeding the subset into later code.
Related: How to create a pandas DataFrame
Related: How to filter rows in a pandas DataFrame
Related: How to set an index in pandas
import pandas as pd orders = pd.DataFrame( { "order_id": ["A101", "A102", "A103", "A104", "A105"], "customer": ["Ada", "Lin", "Maya", "Omar", "Nia"], "region": ["EMEA", "APAC", "AMER", "EMEA", "APAC"], "qty": [3, 12, 7, 2, 15], "total_usd": [150.0, 240.0, 875.0, 95.0, 360.0], "status": ["paid", "paid", "open", "paid", "open"], } ).set_index("order_id") print(f"pandas {pd.__version__}") print() print("BASE") print(orders.to_string()) print() label_rows = ["A101", "A104"] label_columns = ["customer", "total_usd"] label_list = orders.loc[label_rows, label_columns] print("LOC_LABEL_LIST") print(label_list.to_string()) print() label_slice = orders.loc["A102":"A104", ["region", "qty"]] print("LOC_LABEL_SLICE") print(label_slice.to_string()) print() position_rows = [0, 3] position_columns = [0, 3] position_list = orders.iloc[position_rows, position_columns] print("ILOC_POSITION_LIST") print(position_list.to_string()) print() position_slice = orders.iloc[1:4, 1:3] print("ILOC_POSITION_SLICE") print(position_slice.to_string()) print() print("VERIFY") print(f"label rows: {label_list.index.tolist()}") print(f"position shape: {position_list.shape}") print(f"label slice includes A104: {'A104' in label_slice.index}") print(f"iloc row labels: {position_slice.index.tolist()}") print(f"source shape unchanged: {orders.shape}") print(f"A104 total by loc: {orders.loc['A104', 'total_usd']}") print(f"A104 total by iloc: {orders.iloc[3, 3]}")
The order_id index makes row labels distinct from row positions. In a working script, replace the sample orders object with the DataFrame already loaded.
$ python3 select_loc_iloc.py
pandas 3.0.3
BASE
customer region qty total_usd status
order_id
A101 Ada EMEA 3 150.0 paid
A102 Lin APAC 12 240.0 paid
A103 Maya AMER 7 875.0 open
A104 Omar EMEA 2 95.0 paid
A105 Nia APAC 15 360.0 open
LOC_LABEL_LIST
customer total_usd
order_id
A101 Ada 150.0
A104 Omar 95.0
LOC_LABEL_SLICE
region qty
order_id
A102 APAC 12
A103 AMER 7
A104 EMEA 2
ILOC_POSITION_LIST
customer total_usd
order_id
A101 Ada 150.0
A104 Omar 95.0
ILOC_POSITION_SLICE
region qty
order_id
A102 APAC 12
A103 AMER 7
A104 EMEA 2
VERIFY
label rows: ['A101', 'A104']
position shape: (2, 2)
label slice includes A104: True
iloc row labels: ['A102', 'A103', 'A104']
source shape unchanged: (5, 5)
A104 total by loc: 95.0
A104 total by iloc: 95.0
LOC_LABEL_SLICE includes A104 because loc label slices include the stop label. ILOC_POSITION_SLICE stops before row position 4 but still carries the original row labels in the result.