Pandas loc: Select and Update Rows by Label

Use loc with square brackets to select and update rows and columns by labels or Boolean conditions. Keep labels distinct from row positions.

Watch: selecting rows with Pandas loc

Create the 35-row student table 🔝

The embedded student sample uses the same columns as the downloadable CSV. For your file replace StringIO(student_csv) with a path or URL, or use read_excel() for a workbook. See read_csv().

import pandas as pd
import re
from io import StringIO

student_csv = 'id,name,class,mark,gender\n1,John Deo,Four,75,female\n2,Max Ruin,Three,85,male\n3,Arnold,Three,55,male\n4,Krish Star,Four,60,female\n5,John Mike,Four,60,female\n6,Alex John,Four,55,male\n7,My John Rob,Fifth,78,male\n8,Asruid,Five,85,male\n9,Tes Qry,Six,78,male\n10,Big John,Four,55,female\n11,Ronald,Six,89,female\n12,Recky,Six,94,female\n13,Kty,Seven,88,female\n14,Bigy,Seven,88,female\n15,Tade Row,Four,88,male\n16,Gimmy,Four,88,male\n17,Tumyu,Six,54,male\n18,Honny,Five,75,male\n19,Tinny,Nine,18,male\n20,Jackly,Nine,65,female\n21,Babby John,Four,69,female\n22,Reggid,Seven,55,female\n23,Herod,Eight,79,male\n24,Tiddy Now,Seven,78,male\n25,Giff Tow,Seven,88,male\n26,Crelea,Seven,79,male\n27,Big Nose,Three,81,female\n28,Rojj Base,Seven,86,female\n29,Tess Played,Seven,55,male\n30,Reppy Red,Six,79,female\n31,Marry Toeey,Four,88,male\n32,Binn Rott,Seven,90,female\n33,Kenn Rein,Six,96,female\n34,Gain Toe,Seven,69,male\n35,Rows Noump,Six,88,female'

def student_data():
    return pd.read_csv(StringIO(student_csv))

df = student_data()
print(df.head())
print("Shape:", df.shape)

Expected output

   id        name  class  mark  gender
0   1    John Deo   Four    75  female
1   2    Max Ruin  Three    85    male
2   3      Arnold  Three    55    male
3   4  Krish Star   Four    60  female
4   5   John Mike   Four    60  female
Shape: (35, 5)

Use square brackets and labels 🔝

loc is an indexer, not a function. Use df.loc[row_selector, column_selector]. Integers identify index labels; iloc uses integer positions. A colon selects all labels on an axis. Boolean Series align by index labels.

Select one or several columns 🔝

A single column label returns a Series; a list of column labels returns a DataFrame.

df = student_data()
print(df.loc[:, "class"].head())
print(df.loc[:, ["class", "name"]].head())
assert df.loc[:, ["class", "name"]].shape == (35, 2)

Expected output

0     Four
1    Three
2    Three
3     Four
4     Four
Name: class, dtype: str
   class        name
0   Four    John Deo
1  Three    Max Ruin
2  Three      Arnold
3   Four  Krish Star
4   Four   John Mike

Select rows and cells by labels 🔝

With the default index, label 2 is the third row. Use a list to retain a DataFrame. Use at for a single cell on uniquely labelled axes.

df = student_data()
print(df.loc[0, ["class", "name"]])
print(df.loc[[2, 3], ["class", "name"]])
print(df.loc[[2, 3], :])
print("Arnold mark:", df.loc[2, "mark"])
assert df.loc[2, "name"] == "Arnold"

Expected output

class        Four
name     John Deo
Name: 0, dtype: str
   class        name
2  Three      Arnold
3   Four  Krish Star
   id        name  class  mark  gender
2   3      Arnold  Three    55    male
3   4  Krish Star   Four    60  female
Arnold mark: 55

Find one or several named students 🔝

Set name as the index to select name labels. A list selection follows the requested label order. Missing requested labels raise KeyError.

named = student_data().set_index("name")
print(named.loc["Tade Row"])
print(named.loc[["Tade Row", "John Deo"]])
print(named.loc["Arnold", "mark":])
print(named.loc[:, "mark"].head())
print(named.loc["Arnold", :])
assert named.loc["Tade Row", "id"] == 15

Expected output

id          15
class     Four
mark        88
gender    male
Name: Tade Row, dtype: object
          id class  mark  gender
name                            
Tade Row  15  Four    88    male
John Deo   1  Four    75  female
mark        55
gender    male
Name: Arnold, dtype: object
name
John Deo      75
Max Ruin      85
Arnold        55
Krish Star    60
John Mike     60
Name: mark, dtype: int64
id            3
class     Three
mark         55
gender     male
Name: Arnold, dtype: object

Select every matching duplicate label 🔝

A class label can identify many students. Duplicate labels are valid for grouped selection, but cannot identify one unique business record without another key.

by_class = student_data().set_index("class")
print(by_class.loc["Four"])
assert not by_class.index.is_unique

Expected output

       id         name  mark  gender
class                               
Four    1     John Deo    75  female
Four    4   Krish Star    60  female
Four    5    John Mike    60  female
Four    6    Alex John    55    male
Four   10     Big John    55  female
Four   15     Tade Row    88    male
Four   16        Gimmy    88    male
Four   21   Babby John    69  female
Four   31  Marry Toeey    88    male

Select inclusive label ranges 🔝

Label slices include both endpoints. An unsorted unique index with existing bounds follows its current order, not alphabetical order. Nonunique unsorted bounds can be ambiguous; for value ranges use a mask or sort the index under an explicit rule.

by_id = student_data().set_index("id").sort_index()
print(by_id.loc[4:6])
named = student_data().set_index("name")
print(named.loc["Ronald":"Bigy"])
assert by_id.loc[4:6].index.tolist() == [4, 5, 6]
assert named.loc["Ronald":"Bigy"].index.tolist() == ["Ronald", "Recky", "Kty", "Bigy"]

Expected output

          name class  mark  gender
id                                
4   Krish Star  Four    60  female
5    John Mike  Four    60  female
6    Alex John  Four    55    male
        id  class  mark  gender
name                           
Ronald  11    Six    89  female
Recky   12    Six    94  female
Kty     13  Seven    88  female
Bigy    14  Seven    88  female

Select with callable expressions 🔝

A callable receives the current table. Reference a name column before moving it to the index; afterwards use the index explicitly. Boolean results must have compatible labels or the correct array length.

df = student_data()
print(df.loc[lambda x: x["name"].eq("Arnold")])
print(df.loc[lambda x: x["mark"].gt(90)])
print(df.loc[lambda x: x["gender"].eq("male")].head())
print(df.loc[lambda x: x["name"].str.len().gt(10)].head())
named = df.set_index("name")
print(named.loc[lambda x: x.index.str.len() > 10].head())

Expected output

   id    name  class  mark gender
2   3  Arnold  Three    55   male
    id       name class  mark  gender
11  12      Recky   Six    94  female
32  33  Kenn Rein   Six    96  female
   id         name  class  mark gender
1   2     Max Ruin  Three    85   male
2   3       Arnold  Three    55   male
5   6    Alex John   Four    55   male
6   7  My John Rob  Fifth    78   male
7   8       Asruid   Five    85   male
    id         name  class  mark gender
6    7  My John Rob  Fifth    78   male
28  29  Tess Played  Seven    55   male
30  31  Marry Toeey   Four    88   male
             id  class  mark gender
name                               
My John Rob   7  Fifth    78   male
Tess Played  29  Seven    55   male
Marry Toeey  31   Four    88   male

Match a complete name or a substring 🔝

Use eq() for exact values. str.contains() can match partial text. Literal matching with na=False makes missing names nonmatches.

df = student_data()
print(df.loc[df["name"].eq("Alex John")])
print(df.loc[df["name"].str.contains("De", case=False, regex=False, na=False)])
assert df.loc[df["name"].eq("Alex John"), "id"].tolist() == [6]

Expected output

   id       name class  mark gender
5   6  Alex John  Four    55   male
    id      name class  mark  gender
0    1  John Deo  Four    75  female
14  15  Tade Row  Four    88    male

Match or exclude an ending 🔝

Specify missing-value treatment before inversion. With na=False, inversion includes missing names; add notna() when only known names should be kept.

df = student_data()
ends = df["name"].str.endswith("John", na=False)
print(df.loc[ends])
print("Not ending with John IDs:", df.loc[~ends, "id"].tolist())
assert df.loc[ends, "id"].tolist() == [6, 10, 21]

Expected output

    id        name class  mark  gender
5    6   Alex John  Four    55    male
9   10    Big John  Four    55  female
20  21  Babby John  Four    69  female
Not ending with John IDs: [1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]

Match or exclude a beginning 🔝

startswith() is a literal prefix check. Case sensitivity applies to the text as stored.

df = student_data()
starts = df["name"].str.startswith("Jo", na=False)
print(df.loc[starts])
print("Other IDs:", df.loc[~starts, "id"].tolist())
assert df.loc[starts, "id"].tolist() == [1, 5]

Expected output

   id       name class  mark  gender
0   1   John Deo  Four    75  female
4   5  John Mike  Four    60  female
Other IDs: [2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]

Match any of several strings 🔝

Regex alternation matches either term. Escape terms when constructing a regex from literal strings, or combine separate literal masks with |.

df = student_data()
terms = ["Jo", "Al"]
pattern = "|".join(re.escape(term) for term in terms)
any_term = df["name"].str.contains(pattern, na=False)
print(df.loc[any_term])
print("Neither term IDs:", df.loc[~any_term, "id"].tolist())
print("No Jo IDs:", df.loc[~df["name"].str.contains("Jo", regex=False, na=False), "id"].tolist())

Expected output

    id         name  class  mark  gender
0    1     John Deo   Four    75  female
4    5    John Mike   Four    60  female
5    6    Alex John   Four    55    male
6    7  My John Rob  Fifth    78    male
9   10     Big John   Four    55  female
20  21   Babby John   Four    69  female
Neither term IDs: [2, 3, 4, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
No Jo IDs: [2, 3, 4, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]

Require both strings 🔝

Use & for AND and | for OR; Python and/or do not combine whole Series. Both masks must describe the same table.

df = student_data()
jo = df["name"].str.contains("Jo", regex=False, na=False)
al = df["name"].str.contains("Al", regex=False, na=False)
print("Both:")
print(df.loc[jo & al])
print("Either IDs:", df.loc[jo | al, "id"].tolist())
assert df.loc[jo & al, "id"].tolist() == [6]

Expected output

Both:
   id       name class  mark gender
5   6  Alex John  Four    55   male
Either IDs: [1, 5, 6, 7, 10, 21]

Combine text and numeric conditions 🔝

Parentheses separate comparisons. Build named masks when the rule becomes hard to read. See query() for fixed expression-based filters.

df = student_data()
print(df.loc[df["name"].str.contains("Mike", regex=False, na=False) | df["id"].eq(6)])
by_id = df["id"].eq(5)
by_name = df["name"].str.contains("Al", case=False, regex=False, na=False)
low_mark = df["mark"].lt(50)
print(df.loc[by_id | by_name | low_mark])

Expected output

   id       name class  mark  gender
4   5  John Mike  Four    60  female
5   6  Alex John  Four    55    male
    id       name class  mark  gender
4    5  John Mike  Four    60  female
5    6  Alex John  Four    55    male
10  11     Ronald   Six    89  female
18  19      Tinny  Nine    18    male

Understand Boolean Series alignment 🔝

loc aligns a Boolean Series to the row index. A Boolean list or array is positional and must match the row count. Build masks from the current table to avoid unintended alignment.

small = pd.DataFrame({"amount": [10, 20, 30]}, index=[101, 102, 103])
mask = pd.Series([True, False, True], index=[103, 101, 102])
print(small.loc[mask])
print("Array selection:")
print(small.loc[mask.to_numpy()])
assert small.loc[mask].index.tolist() == [102, 103]

Expected output

     amount
102      20
103      30
Array selection:
     amount
101      10
103      30

Inspect the final rows with tail() 🔝

Use tail() for final positions. A negative loc label is a label lookup, not a shortcut for the last row.

df = student_data()
print(df.tail(1))
print(df.tail(5))
assert df.tail(1)["id"].tolist() == [35]

Expected output

    id        name class  mark  gender
34  35  Rows Noump   Six    88  female
    id         name  class  mark  gender
30  31  Marry Toeey   Four    88    male
31  32    Binn Rott  Seven    90  female
32  33    Kenn Rein    Six    96  female
33  34     Gain Toe  Seven    69    male
34  35   Rows Noump    Six    88  female

Add a row with an unused label 🔝

df.loc[len(df)] is appropriate only when that label is unused, as with this fresh default index. On a filtered or differently indexed table it can overwrite an existing label. Validate the label and match values to the column order.

df = student_data()
new_label = len(df)
if new_label in df.index:
    raise ValueError("New label already exists")
new_list = [36, "new name", "Four", 77, "male"]
df.loc[new_label] = new_list
print(df.tail(2))
assert len(df) == 36 and df.loc[new_label, "id"] == 36

Expected output

    id        name class  mark  gender
34  35  Rows Noump   Six    88  female
35  36    new name  Four    77    male

Update a column under a condition 🔝

Assign with one loc operation. Each example below uses a fresh table. In this demonstration the rule sets high marks to 100; a real grade change requires an approved rule.

df = student_data()
df.loc[df["mark"].ge(90), "mark"] = 100
print(df.loc[df["mark"].eq(100)])
df = student_data()
condition = df["mark"].ge(90) & df["class"].eq("Six")
df.loc[condition, "mark"] = 100
print("Class Six updates:")
print(df.loc[condition])

Expected output

    id       name  class  mark  gender
11  12      Recky    Six   100  female
31  32  Binn Rott  Seven   100  female
32  33  Kenn Rein    Six   100  female
Class Six updates:
    id       name class  mark  gender
11  12      Recky   Six   100  female
32  33  Kenn Rein   Six   100  female

Update a category or name directly 🔝

Changing the in-memory DataFrame does not write back to the original CSV. Keep source values or a change record when corrections must be traceable.

df = student_data()
condition = df["mark"].ge(90) & df["class"].eq("Six")
df.loc[condition, "class"] = "Seven"
print(df.loc[condition])
df = student_data()
df.loc[df["name"].eq("Rows Noump"), "name"] = "New Name"
print(df.tail(1))
assert df.loc[34, "name"] == "New Name"

Expected output

    id       name  class  mark  gender
11  12      Recky  Seven    94  female
32  33  Kenn Rein  Seven    96  female
    id      name class  mark  gender
34  35  New Name   Six    88  female

Practical example: select paid orders and review unknown amounts 🔝

This synthetic table preserves invalid amounts for review. Clean numeric values before combining selection rules. The Data Cleaning hub covers related validation steps.

raw = pd.DataFrame({
    "order_id": [101, 102, 103, 104, 105],
    "status": ["paid", "paid", "test", "paid", "paid"],
    "amount": ["20", "35", "99", "bad", "10"]
})
amount = pd.to_numeric(raw["amount"], errors="coerce")
review = raw.loc[amount.isna()].copy()
eligible = raw["status"].eq("paid") & amount.ge(20)
selected = raw.loc[eligible, ["order_id", "amount"]].copy()
selected["amount"] = amount.loc[selected.index]
print(selected)
print("Review IDs:", review["order_id"].tolist())
assert selected["order_id"].tolist() == [101, 102]
assert selected["amount"].sum() == 55

Expected output

   order_id  amount
0       101    20.0
1       102    35.0
Review IDs: [104]

Assign a review flag and export the intended records 🔝

Set flags on the working table with one loc assignment. Export selected orders separately from the review data; unknown amounts should not become zero merely to pass a numeric rule. See Input and Output.

working = raw.copy()
working["needs_review"] = False
working.loc[amount.isna(), "needs_review"] = True
print(working)
print(selected.to_csv(index=False).rstrip())
assert working["needs_review"].sum() == 1
# Optional Colab exports:
# selected.to_csv("selected_orders.csv", index=False)
# review.to_csv("review_orders.csv", index=False)

Expected output

   order_id status amount  needs_review
0       101   paid     20         False
1       102   paid     35         False
2       103   test     99         False
3       104   paid    bad          True
4       105   paid     10         False
order_id,amount

101,20.0

102,35.0

Common questions 🔝

Why did loc[2] select a different row after indexing? 2 is a label. Are slice endpoints included? Yes, label slices include both bounds when resolvable. Why did selection return multiple rows? The row label may be duplicated. Why does a mask fail? Its index may be unalignable, or its array length may differ. Why did assignment replace a row? The requested label already existed. Should I edit a chained selection? Assign directly through the source DataFrame loc indexer. Continue with mask(), where(), NumPy where() and DataFrames.

Exercise: select strong Class Four students 🔝

From a fresh student table, select Class Four students with marks at least 80 and return only id, name and mark. Predict the result before running the solution.

Exercise solution 🔝

Use one mask for both conditions and a column list to keep a DataFrame.

df = student_data()
mask = df["class"].eq("Four") & df["mark"].ge(80)
answer = df.loc[mask, ["id", "name", "mark"]]
print(answer)
assert answer["id"].tolist() == [15, 16, 31]

Expected output

    id         name  mark
14  15     Tade Row    88
15  16        Gimmy    88
30  31  Marry Toeey    88

Practice in Google Colab 🔝

Open in Google Colab View on GitHub
Run the examples in order, change labels and filtering conditions, then inspect selections and independent updates. Save your own copy to keep edits. All sample tables are included in the notebook.

Reference: Pandas DataFrame.loc documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer