Selecting, filtering and cleaning

loc, iloc, boolean filters, missing values and type conversion — the routine work that makes up most of a data task.

Selecting columns and rows

df["name"]                 # one column -> Series
df[["name", "score"]]      # several -> DataFrame

df.loc[df["score"] > 80, ["name", "score"]]   # label-based, with a condition
df.iloc[0:3, 0:2]                              # position-based
df.at[0, "name"]                               # single scalar by label
df.iat[0, 0]                                   # single scalar by position
⚠️
Use .loc/.iloc, not chained indexing like df[df.a > 1]["b"] = 0. Chained assignment writes to a temporary copy and does nothing — pandas warns about this for good reason.

Filtering

df[df["score"] > 80]
df[(df["team"] == "A") & (df["score"] > 80)]     # & and |, with parentheses
df[df["team"].isin(["A", "C"])]
df[df["name"].str.startswith("A")]
df[df["score"].between(70, 90)]
df[~df["name"].isna()]
  • Use &, |, ~ — not and/or, which cannot handle arrays.
  • Wrap each comparison in parentheses; precedence will otherwise bite.
  • query() is often more readable: df.query("score > 80 and team == 'A'").

Cleaning

df.isna().sum()
df.dropna(subset=["score"])                  # drop rows missing a key field
df["score"] = df["score"].fillna(df["score"].median())
df["team"] = df["team"].fillna("unknown")

df = df.drop_duplicates(subset=["name"], keep="first")

df["score"] = pd.to_numeric(df["score"], errors="coerce")   # bad values -> NaN
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df["name"] = df["name"].str.strip().str.title()
💡
Prefer errors="coerce" over letting a conversion raise: it turns unparsable values into NaN so you can see and count the damage instead of the whole job failing.

Deriving new columns

df["pass"] = df["score"] >= 80
df["bucket"] = pd.cut(df["score"], bins=[0, 60, 80, 100], labels=["low", "mid", "high"])
df["initials"] = df["name"].str[0]

df.assign(score_pct=lambda d: d["score"] / d["score"].max())   # chainable
df.apply(lambda r: r["score"] * 2, axis=1)                     # slower; last resort

FAQ

Why does SettingWithCopyWarning appear?
You are assigning into a slice that may be a copy. Fix the root cause: select with .loc in one step, or call .copy() when you truly intend a standalone frame.
How do I rename or reorder columns?
df.rename(columns={...}) and df.reindex(columns=[...]) (or df[["a","b"]]) respectively.

Series and DataFrame Grouping, joining and reshaping

Last refreshed 2026-09-18.