In this lesson: Create DataFrames from several sources and manage the index deliberately.
You will usually load a DataFrame from a file, but building one by hand is how you test an idea in five seconds.
Three ways to build one
import pandas as pd
# from a dict of columns -- the most common
df = pd.DataFrame({
"name": ["Aline", "Jean", "Uwase"],
"district": ["Gasabo", "Huye", "Gasabo"],
"mark": [78, 41, 65],
})
# from a list of records (rows) -- what an API usually returns
rows = [
{"name": "Aline", "mark": 78},
{"name": "Jean", "mark": 41},
]
df2 = pd.DataFrame(rows)
# from a list of lists, with explicit column names
df3 = pd.DataFrame([["Aline", 78], ["Jean", 41]], columns=["name", "mark"])
Looking at the shape
df.shape # (3, 3) -- rows, columns
df.columns # Index(['name', 'district', 'mark'], dtype='object')
df.dtypes # the type of each column
df.index # RangeIndex(start=0, stop=3, step=1)
Picking columns
df["mark"] # one column -> a Series
df[["name", "mark"]] # several columns -> a DataFrame
df["mark"] and df[["mark"]] hold the same numbers in different container types.
Adding and removing columns
df["passed"] = df["mark"] >= 50 # a boolean column, computed
df["final"] = (df["mark"] * 1.1).round(1)
df["grade"] = pd.cut(df["mark"], bins=[0, 49, 69, 100],
labels=["Fail", "Pass", "Distinction"])
df = df.drop(columns=["final"]) # remove
df = df.rename(columns={"mark": "term_mark"})
pd.cut turns a numeric column into labelled bands — age groups, grade bands, price brackets. You will use it constantly.
The index, taken seriously
By default the index is 0, 1, 2… You can make a meaningful column the index instead:
df = df.set_index("name")
print(df.loc["Jean"]) # look a row up by its label
df = df.reset_index() # push it back to being a normal column
Two rules worth learning now:
- After filtering, the index keeps the original numbers — rows 0, 3, 7. That is deliberate: it tells you where the rows came from. Call
.reset_index(drop=True)when you want a clean 0..n again. - Two DataFrames combine on the index, not on row position. Mismatched indexes are why an arithmetic result can come out full of
NaN.
Copies and views
small = df[df["mark"] > 50] # may be a view onto df
small["bonus"] = 5 # SettingWithCopyWarning
small = df[df["mark"] > 50].copy() # explicit copy -- no warning
small["bonus"] = 5 # safe
SettingWithCopyWarning means pandas cannot tell whether you meant to change the original. Add .copy() when you slice a subset you intend to modify, and the warning disappears for good.
Try it yourself
Build a ten-row DataFrame of students with name, district and mark. Add a passed boolean and a banded grade column with pd.cut. Set name as the index, look one student up with .loc, then reset the index.