DataLens

Adding, renaming, and dropping columns

Shape a table for your analysis: calculated columns, conditional columns with np.where, assign() for chaining, rename, drop and reorder.

Level: Intermediate · 9 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

Raw data rarely has the exact columns your analysis needs. You'll add calculated columns (margin, VAT, a "big order" flag), rename cryptic export names (TGL_TRX → order_date), and drop columns nobody uses. These operations are simple, but doing them cleanly (in one place, with clear names, without surprising side effects) is what makes an analysis easy to follow.

import pandas as pd
import numpy as np

sales = pd.read_csv("sales.csv")
lines = sales[["order_id", "product", "category", "quantity", "unit_price", "revenue", "status"]].copy()

.copy() makes lines an independent DataFrame, so adding columns to it never affects sales (and avoids the warning from the last module).

Adding a calculated column

Assign to a new column name. The right-hand side is usually vectorised maths on other columns:

lines["vat"] = (lines["revenue"] * 0.11).round().astype(int)
lines["revenue_incl_vat"] = lines["revenue"] + lines["vat"]
print(lines[["product", "revenue", "vat", "revenue_incl_vat"]].head(3))
             product  revenue     vat  revenue_incl_vat
0     Desk Organizer    89000    9790             98790
1  Arabica Gayo 250g   285000   31350            316350
2   Pour-over Kettle   960000  105600           1065600

.round() rounds, and .astype(int) converts the floats back to whole numbers. A single value works too: lines["currency"] = "IDR" fills every row with it.

Conditional columns

For a two-way choice, np.where(condition, value_if_true, value_if_false) from NumPy is the standard tool:

lines["size"] = np.where(lines["revenue"] >= 300000, "large", "small")
print(lines["size"].value_counts())
size
small    52
large    19
Name: count, dtype: int64

For several bands, pd.cut turns numbers into labelled ranges:

lines["price_band"] = pd.cut(
    lines["unit_price"],
    bins=[0, 60000, 150000, float("inf")],
    labels=["budget", "mid", "premium"],
)
print(lines[["product", "unit_price", "price_band"]].head(4))
             product  unit_price price_band
0     Desk Organizer       89000        mid
1  Arabica Gayo 250g       95000        mid
2   Pour-over Kettle      320000    premium
3     Scented Candle       75000        mid

bins are the edges: 0 to 60,000 is "budget", 60,000 to 150,000 "mid", and anything above "premium". Each bin includes its right edge (so 60,000 counts as budget).

For arbitrary mappings, .map() with a dictionary translates values (module 12 covers it fully):

category_owner = {"Coffee": "Maya", "Kitchen": "Maya", "Stationery": "Agus", "Electronics": "Agus", "Home": "Maya"}
lines["owner"] = lines["category"].map(category_owner)

assign: add columns without changing the original

df.assign(...) returns a new DataFrame with extra columns. It doesn't touch df, which makes it perfect for chaining steps together (module 20) and for keeping the original intact:

enriched = lines.assign(
    margin_rate=0.4,
    est_margin=lambda d: (d["revenue"] * d["margin_rate"]).round().astype(int),
)
print(enriched[["revenue", "margin_rate", "est_margin"]].head(2))
print("est_margin" in lines.columns)
   revenue  margin_rate  est_margin
0    89000          0.4       35600
1   285000          0.4      114000
False

The lambda d: ... form receives the DataFrame as built so far, so est_margin can use margin_rate, which was created a moment earlier in the same assign.

Renaming columns

Exports from other systems often come with column names like TGL_TRX or NM PRODUK, which mean something to whoever built that system but are awkward to type and hard to read in your code. Renaming them once, right after loading, makes everything after that easier. Pass rename a dictionary that maps each old name to its new name:

export = pd.DataFrame({"TGL_TRX": ["01/07/2024"], "NM PRODUK": ["Kopi Susu"], "Qty": [2]})
export = export.rename(columns={"TGL_TRX": "order_date", "NM PRODUK": "product", "Qty": "quantity"})
print(list(export.columns))
['order_date', 'product', 'quantity']

To clean every name at once (lowercase, underscores instead of spaces), transform the column labels directly:

raw = pd.DataFrame(columns=["Order ID", " Customer Name ", "Total (Rp)"])
raw.columns = (
    raw.columns.str.strip()
    .str.lower()
    .str.replace(r"[^a-z0-9]+", "_", regex=True)
    .str.strip("_")
)
print(list(raw.columns))
['order_id', 'customer_name', 'total_rp']

Consistent snake_case column names mean you never have to remember whether it was "Total (Rp)" or "total". Many analysts make this the first line after every read_csv.

Dropping columns

Helper columns you only needed for a calculation, like the VAT columns from earlier, can clutter the final table. drop removes the columns you name:

slim = lines.drop(columns=["vat", "revenue_incl_vat"])
print(slim.shape, lines.shape)
(71, 10) (71, 12)

drop returns a new frame. errors="ignore" stops it complaining if a column doesn't exist. Selecting the columns you keep (lines[[...]]) is often clearer than listing what to drop, because the result's shape is visible in the code.

Reordering columns

Column order matters more than it seems. The person reading your table looks at the left-hand columns first, so the most important ones (the order id, the product, the revenue) should come first. There's no special reorder method; you just select the columns in the order you want:

first = ["order_id", "product", "revenue"]
ordered = lines[first + [c for c in lines.columns if c not in first]]
print(list(ordered.columns)[:5])
['order_id', 'product', 'revenue', 'category', 'quantity']

Why this matters for clean code

Give derived columns names that say exactly what they contain (revenue_incl_vat, not rev2), standardise column names once at load time, and prefer assign when you want each step to produce a new, clearly named result. Avoid quietly modifying a shared DataFrame from several places: when a column appears "from nowhere", the next reader has to hunt for where it was created.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Handling missing data (NaN, fillna, dropna) →