Create New Columns
Learn how to create new columns from existing data in pandas — practical steps, options, and pitfalls for data science with Python.
Focus: create new columns from existing data
Ever stared at a raw dataset and thought, “I wish this column summed those two, or told me if the price changed, or flagged this row as urgent”? Raw data almost never arrives in the shape you need. You’ll spend a large part of your data science life creating new columns from existing data — combining, transforming, and deriving fields that unlock the real signal. This lesson gives you the pandas toolbox to do that cleanly, efficiently, and without the silent bugs that quietly corrupt your analysis.
The problem this lesson solves
When you load a CSV or pull data from an API, you get exactly what the source gave you — not what your analysis needs. Maybe you need:
- A
total_pricefromquantity * unit_price - A
statuscolumn that says "high", "medium", or "low" based on a numeric score - A
date_diffbetween an order date and a ship date
Writing this logic outside your DataFrame means copying values around, losing alignment, and creating errors the moment rows shift or get filtered. The core problem: you need to derive new fields inside your dataset, reliably, and with code that’s obvious to your future self (and your teammates).
This lesson focuses on pandas — the de facto tool for tabular data in Python. By the end, you’ll confidently turn existing columns into exactly the features your analysis or model needs.
Core concept / mental model
Think of your DataFrame as a spreadsheet on steroids. Each column is a series of values, all aligned by row index. Creating a new column is like adding a new column to that spreadsheet — but here, you write formulas (Python expressions) instead of clicking and dragging.
The mental model breaks down into three operations:
- Derive — compute a new value from one or more existing columns (e.g.,
df['area'] = df['width'] * df['height']) - Transform — map or convert existing values (e.g.,
df['lower_name'] = df['name'].str.lower()) - Categorize — assign labels or flags based on conditions (e.g.,
df['is_adult'] = df['age'] >= 18)
Importantly, pandas aligns on the index — so even messier data sources line up correctly. You can also use df.assign() for a functional style, which returns a new DataFrame rather than modifying your original — a habit that prevents accidental data corruption.
Pro tip: Always ask: Do I want to modify in place (
df['col'] = ...) or return a new object (df.assign(...))? In a pipeline, preferassignfor a clean, functional flow.
How it works step by step
The mechanics of adding a column are straightforward, but the choices matter. Here’s the logical sequence:
- Load or have your DataFrame ready — usually from a CSV, database, or API.
- Identify the existing columns you’ll use as inputs.
- Write your expression — a Python operation, a string method, a NumPy function, or a lambda.
- Assign the result to a new column name — either with
df['new'] = ...ordf.assign(new=...). - Check the result —
print(df.head())ordf.info()to confirm the new column exists and looks right.
Let’s see each step in action.
Hands-on walkthrough
We’ll use a small sales dataset to practice. First, create a DataFrame from a list of dicts — this is a realistic starting point.
import pandas as pd
sales = pd.DataFrame({
'product': ['Laptop', 'Keyboard', 'Mouse', 'Monitor'],
'quantity': [2, 5, 10, 1],
'unit_price': [999.0, 49.0, 25.0, 249.0],
'order_date': ['2024-01-15', '2024-01-16', '2024-01-17', '2024-01-18']
})
print(sales)
Output:
product quantity unit_price order_date
0 Laptop 2 999.0 2024-01-15
1 Keyboard 5 49.0 2024-01-16
2 Mouse 10 25.0 2024-01-17
3 Monitor 1 249.0 2024-01-18
Now, create a new column total_price by multiplying two existing columns:
# Option A: direct assignment (modifies the original DataFrame)
sales['total_price'] = sales['quantity'] * sales['unit_price']
sales['is_expensive'] = sales['total_price'] > 500
print(sales)
Output:
product quantity unit_price order_date total_price is_expensive
0 Laptop 2 999.0 2024-01-15 1998.0 True
1 Keyboard 5 49.0 2024-01-16 245.0 False
2 Mouse 10 25.0 2024-01-17 250.0 False
3 Monitor 1 249.0 2024-01-18 249.0 False
But what about more complex logic? You can use apply with a lambda or a custom function to compute values that don’t fit a simple arithmetic expression.
# Determine a shipping label based on total price
def shipping_label(price):
if price >= 1000:
return 'Free Express'
elif price >= 200:
return 'Free Standard'
else:
return 'Standard'
sales['shipping'] = sales['total_price'].apply(shipping_label)
print(sales[['product', 'total_price', 'shipping']])
Output:
product total_price shipping
0 Laptop 1998.0 Free Express
1 Keyboard 245.0 Free Standard
2 Mouse 250.0 Free Standard
3 Monitor 249.0 Free Standard
Adding date-based columns is just as easy. Convert the date strings to datetime, then add a day_of_week column.
sales['order_dt'] = pd.to_datetime(sales['order_date'])
sales['day_of_week'] = sales['order_dt'].dt.day_name()
print(sales[['product', 'order_date', 'day_of_week']])
Output:
product order_date day_of_week
0 Laptop 2024-01-15 Monday
1 Keyboard 2024-01-16 Tuesday
2 Mouse 2024-01-17 Wednesday
3 Monitor 2024-01-18 Thursday
Pro tip: Use
df.assign()in a method chain to keep your code tidy:
sales = sales.assign(
total_price=sales['quantity'] * sales['unit_price'],
is_expensive=sales['quantity'] * sales['unit_price'] > 500,
shipping=sales['quantity'] * sales['unit_price'].apply(shipping_label)
)
Compare options / when to choose what
You have several ways to add a column — each fits a different scenario. Here’s the decision table:
| Method | Best for | Returns | Example |
|---|---|---|---|
Direct assignment df['col'] = ... |
Simple arithmetic, one-time modifications | Modified original DataFrame | df['total'] = df['a'] + df['b'] |
df.assign(col=...) |
Clean pipelines, keeping the original intact | New DataFrame (copy) | df.assign(total=df['a']+df['b']) |
df['col'].apply(fn) |
Custom logic (if/else, loops) on one column | Series of results | df['status'] = df['score'].apply(lambda x: 'high' if x > 80 else 'low') |
df.apply(fn, axis=1) |
Row-wise logic using multiple columns | Series of results | df.apply(lambda r: r['a'] * r['b'], axis=1) |
np.where(condition, a, b) |
Fast vectorized if-else | array/Series | df['flag'] = np.where(df['x'] > 0, 'pos', 'neg') |
When to choose what:
- Vectorized arithmetic (e.g.,
df['a'] * 2) — fastest, always preferred when possible. assign— when you’re building a multi-step transformation pipeline.applywith axis=1 — for complex row-based calculations (but be aware it’s slower for large DataFrames).np.where— for simple conditional flags without writing a function.
Troubleshooting & edge cases
Creating columns goes wrong in predictable ways. Here’s how to fix them:
1. KeyError: 'column_name'
You’re referencing a column that doesn’t exist — usually a typo or you forgot to inspect your data.
# Wrong: 'quantty' is misspelled
df['total'] = df['quantty'] * df['unit_price']
Fix: Print df.columns and check exact names before referencing.
2. ValueError: operands could not be broadcast together
You’re trying to combine Series of different lengths — often because you filtered one column without resetting the index.
# Wrong: y is filtered, x is not
y = df[df['qty'] > 0]
df['new'] = df['price'] * y['qty']
Fix: Use reset_index(drop=True) on the filtered Serie or avoid filtering separate columns entirely.
3. Silently wrong values (the worst one)
If you use a Python list directly instead of a Series, pandas aligns by position, not by index. If your index is not 0,1,2..., the new column will be misaligned.
# Wrong: uses a list, index gaps cause misalignment
df = df[df['qty'] > 0] # index now 0 and 2
df['total'] = [100, 200] # intends qty*unit_price, but places 100 and 200 at rows 0 and 1
Fix: Always compute from Series (e.g., df['qty'] * df['unit_price']) or use reset_index(drop=True) after filtering.
4. Date columns remain strings
If you try sales['order_dt'].dt.day_name() but order_dt is a string, you’ll get an AttributeError.
Fix: Convert with pd.to_datetime() first.
What you learned & what's next
You can now create new columns from existing data — the bread-and-butter of data preparation. You’ve learned:
- How to derive numeric columns with arithmetic (covered learning objective 1: explain the core idea)
- How to use
applyfor custom logic andassignfor clean pipelines - How to handle date-derived columns and simple flags
- How to complete a practical exercise in the hands-on section (learning objective 2)
- Real-world troubleshooting: alignment,
KeyError, and dtype pitfalls
What’s next: In the next lesson, you’ll learn how to filter and query your data — selecting the exact rows you need for analysis. You now have the tools to shape datasets, and filtering is the next step toward clean, focused datasets.
Practice recap
Mini exercise: Load the sales data from this lesson as a DataFrame. Add a total_price column, then create a category column that assigns 'High' when total_price > 500, 'Medium' when > 200, and 'Low' otherwise. Finally, use assign to re-create the same DataFrame without modifying the original, and print both to compare. Verify your results with head() and check for index alignment if you filter first.
Common mistakes
- Using a Python list instead of a Series when assigning a new column — pandas aligns by position, so if the index has gaps (e.g., after filtering), values get placed in the wrong rows.
- Ignoring the dtype of a column before doing math — e.g., trying to multiply a string column by a number yields a
TypeErroror concatenation instead. Usepd.to_numeric()first. - Overusing
df.apply(axis=1)for simple arithmetic — it is much slower than vectorized column operations. Only use it for genuine row-wise logic that can't be vectorized. - Forgetting to reset the index after filtering, then assigning a column — causes
ValueErrorabout operand lengths or silent misalignment.
Variations
- Instead of
df['col'] = ..., usedf.assign(col=...)for a functional, non-mutating style — ideal in method chains. - Use
numpy.where()ornumpy.select()for fast conditional column creation withoutapply. - Use
pd.cut()to create categorical bins from continuous numeric columns (e.g., age groups or price bands).
Real-world use cases
- In e-commerce analytics, derive
revenue = quantity * unit_priceandmargin = revenue - costper order for a sales dashboard. - In a churn model, compute
days_since_last_purchasefrom apurchase_datecolumn to create a key feature. - In a marketing campaign, create an
is_high_valueflag by applying a threshold to a customer lifetime value column.
Key takeaways
- Creating a new column from existing data is the backbone of feature engineering — it lets you encode domain knowledge into your dataset.
- Prefer vectorized operators (+, *, >, etc.) over
applyfor speed and readability. df.assign()returns a new DataFrame, which prevents accidental mutation — a good habit for pipelines.- Always check for index alignment issues, especially after filtering rows.
- Converting to datetime (with
pd.to_datetime) unlocks powerful date-based column creation via the.dtaccessor.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.