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.

Steps to select pandas DataFrame rows and columns with loc and iloc:

  1. Save a short loc and iloc selection script.
    select_loc_iloc.py
    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.

  2. Run the script and compare the label-based and position-based selections.
    $ 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.