Q&A
How to create a new column with pandas
Overview
A pandas DataFrame is a 2-D table; columns are Series. Creating a new column is one of the most frequent data-wrangling tasks - deriving features, flags, or transformed values from existing data. Because pandas operations are vectorized, you usually compute an entire column at once without looping.
graph LR
A[Existing DataFrame] --> B{New column source}
B --> C[Constant scalar]
B --> D[Computation on existing columns]
B --> E[apply / map a function]
B --> F[Conditional: np.where]
C & D & E & F --> G[DataFrame with new column]
style B fill:#1e1b4b,color:#fff
Setup
import pandas as pd
import numpy as np
df = pd.DataFrame({
"name": ["Ada", "Linus", "Grace"],
"price": [100, 250, 80],
"qty": [2, 1, 5]
})
1. Assign a constant
df["currency"] = "USD" # every row gets "USD"
2. Derive from existing columns (vectorized)
The most common case - arithmetic across columns happens elementwise:
df["total"] = df["price"] * df["qty"] # 200, 250, 400
No loop needed; pandas applies the multiplication row by row in fast compiled code.
3. Assign a list or Series
df["region"] = ["EU", "EU", "US"] # length must match number of rows
If you assign a Series, pandas aligns by index, not position - a subtle but important behavior.
4. Apply a function with .apply()
For row-wise logic that isn't a simple vectorized expression:
df["price_band"] = df["price"].apply(lambda p: "high" if p > 150 else "low")
# across multiple columns -> axis=1
df["summary"] = df.apply(lambda row: f"{row['name']}: {row['total']}", axis=1)
.apply() is flexible but slower than vectorized operations; prefer vectorization when possible.
5. Conditional columns with np.where
A fast, vectorized if/else:
df["expensive"] = np.where(df["price"] > 150, "yes", "no")
For multiple conditions, use np.select:
conditions = [df["price"] > 200, df["price"] > 100]
choices = ["premium", "standard"]
df["tier"] = np.select(conditions, choices, default="budget")
6. The assign() method (chainable, non-mutating)
assign() returns a new DataFrame, which is ideal for method chaining and avoiding accidental mutation:
df2 = df.assign(
total=df["price"] * df["qty"],
with_tax=lambda d: d["total"] * 1.08 # can reference columns created earlier
)
7. Insert at a specific position with insert()
By default new columns go to the end; insert() places one at a chosen index:
df.insert(1, "sku", ["A1", "B2", "C3"]) # insert at column position 1
8. Using .loc[]
.loc can create a column too, and is the recommended way to set values conditionally without the "SettingWithCopyWarning":
df.loc[:, "in_stock"] = df["qty"] > 0
df.loc[df["price"] > 200, "flag"] = "review" # only some rows; others become NaN
Mapping categorical values with .map()
region_names = {"EU": "Europe", "US": "United States"}
df["region_full"] = df["region"].map(region_names)
Why This Matters in Interviews
Data-engineering and analytics interviews love this because it reveals whether you think vectorized (fast, idiomatic) or loop-based (slow, un-Pythonic). Know the simple df['x'] = ... assignment, vectorized arithmetic, np.where/np.select for conditionals, and assign() for chaining. Explaining index alignment when assigning a Series is a strong detail.
Common Pitfalls
- Index alignment surprises - assigning a
Seriesaligns by index; mismatched indices produceNaN. - Length mismatch - assigning a list whose length ≠ number of rows raises an error.
SettingWithCopyWarning- caused by chained indexing (df[df.x>0]['y'] = ...); use.loc[]instead.- Overusing
.apply()where a vectorized operation would be far faster.
References
- pandas DataFrame docs: https://pandas.pydata.org/docs/reference/frame.html
- pandas "Indexing and selecting data": https://pandas.pydata.org/docs/user_guide/indexing.html