DataFrames with pandas
Load a table, pick rows and columns, and summarize groups in a few lines.
Why it matters
Almost every AI project starts with a table: customers, documents, prices, model scores. pandas is the Python library for tables, and the machine learning phase uses it in nearly every lesson. It's also one of the most-used libraries in all of Python, so time spent here pays off far beyond AI: reports, data cleaning, automating a spreadsheet you used to edit by hand.
What you'll be able to do
- Load a CSV into a DataFrame and look at its shape, columns and first rows
- Pick columns, and pick rows with conditions combined by & and |
- Add calculated columns, sort, and find the largest values
- Count categories and summarize groups with value_counts and groupby
What it covers
- A spreadsheet you can program
- Loading a CSV
- Columns and rows
- Rows that need attention
- New columns, sorting and the top few
- Counting and grouping
- Missing values, and reading one cell
- A week of trail temperatures
- Summarize by category
- Worked example: which categories grew?
- Saving results, and a warning about loops
A spreadsheet you can program
A DataFrame is pandas' table: rows and named columns, like a sheet in a spreadsheet. Each column is a Series, a list of values with a name, which behaves much like a NumPy array. You can build a DataFrame from a dictionary of columns:
import pandas as pd df = pd.DataFrame({ "name": ["Ridgeline tent", "Trail mug", "Camp stove", "Rain jacket"], "category": ["tents", "camp kitchen", "camp kitchen", "apparel"], "price": [289, 14, 65, 120], "rating": [4.6, 4.8, 4.2, 4.5], }) print(df) print(df.shape) print(list(df.columns))
import pandas as pd is the universal habit, so code you read elsewhere looks the same. The printout shows the columns across the top and an index down the left: the row labels, 0 to 3 here. df.shape is (rows, columns), and df.columns lists the column names.
Unlike a spreadsheet, nothing here is a formula in a cell. You ask the table questions in code, and you can run the same questions on next month's file without clicking anything.
Click a column to see the pandas that selects it. Then try Filter rows and Group and summarize: each shows the code and what it gives back. The water filter has no rating yet, which pandas shows as NaN.
Loading a CSV
Real tables arrive as files. pd.read_csv reads a CSV file into a DataFrame, using the first row as the column names:
import pandas as pd with open("stock.csv", "w") as f: f.write("sku,item,stock,price\nTE-101,1-person tent,14,195\nPA-108,daypack 18L,0,55\nAP-122,rain jacket,31,120\n") df = pd.read_csv("stock.csv") print(df.head()) print(df.describe())
The first lines write a small file so the example has something to read. head() shows the first five rows, the quickest way to see what you're dealing with. describe() summarizes every number column: the count, the mean, the spread (std), and the smallest, middle and largest values.
CSV files often live on the web rather than on your computer. read_csv reads from a web address just as it reads a file. The practice shop publishes its full 40-product catalog at one:
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") print(catalog.shape) print(catalog[["sku", "name", "price", "rating"]].head(3))
import pandas as pd df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) print(df.shape, len(df))
Show the answer
(3, 2) 3
shape is (rows, columns): 3 rows and 2 columns. len(df) counts rows.
Columns and rows
Square brackets with one name give one column, a Series. With a list of names, you get a smaller DataFrame:
catalog["price"] # one column: a Series catalog[["name", "price"]] # two columns: a DataFrame
To pick rows, write a condition on a column. It gives a Series of True and False, one per row, called a boolean mask. Putting the mask in square brackets keeps the rows where it's True:
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") cheap = catalog["price"] < 60 print(cheap.head(4).tolist()) print(catalog[cheap][["name", "price"]]) popular_tents = catalog[(catalog["category"] == "tents") & (catalog["rating"] >= 4.4)] print(len(popular_tents), "well-rated tents")
Combine conditions with & (and) and | (or), not the words and and or, and put each condition in brackets. pandas works on whole columns at once, like NumPy, and the words only work on single values.
import pandas as pd df = pd.DataFrame({"price": [10, 60, 25, 90], "stock": [0, 5, 3, 0]}) print(len(df[(df["price"] > 20) & (df["stock"] > 0)]))
Show the answer
2
Rows 1 (60, 5) and 2 (25, 3) pass both conditions. Rows 0 and 3 fail one each.
Rows that need attention
From df, set restock to the rows with stock below 5, and pricey to the rows priced over 100 or rated below 4. Use boolean masks.
import pandas as pd df = pd.DataFrame({ "item": ["tent", "mug", "stove", "jacket", "pack"], "price": [289, 14, 65, 120, 165], "rating": [4.6, 4.8, 3.9, 4.5, 4.4], "stock": [3, 40, 12, 0, 8], }) restock = df pricey = df
New columns, sorting and the top few
A new column is a calculation on old ones, written once for every row:
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") catalog["margin"] = catalog["price"] - catalog["cost"] catalog["revenue_q2"] = catalog["price"] * catalog["units_q2"] print(catalog.sort_values("margin", ascending=False)[["name", "margin"]].head(3)) print(catalog.nlargest(3, "revenue_q2")[["sku", "revenue_q2"]]) best = catalog["margin"].idxmax() print(catalog.loc[best, "name"])
sort_valuessorts by a column;ascending=Falseputs the largest first.nlargest(3, "revenue_q2")is a shortcut for "sort, then take the top 3".idxmax()gives the index label of the largest value, and.loc[row, column]reads one cell by its labels.
None of this changes the original order of catalog: sort_values gives back a new, sorted DataFrame. That's the pattern throughout pandas.
Counting and grouping
Two questions come up in almost every analysis: "how many of each?" and "what's the average per group?"
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") print(catalog["category"].value_counts()) print(catalog.groupby("category")["price"].mean().round(1))
value_counts counts each distinct value, most common first. groupby("category") splits the table into one group per category; ["price"].mean() then works out each group's average price. The result is a Series with the categories as its index.
Notice that rating has some empty cells, for products with no reviews yet. pandas shows them as NaN, "not a number", and mean() skips them. That's usually what you want, but it's worth knowing: a mean over 37 ratings is not a mean over 40 products.
Missing values, and reading one cell
Real tables have gaps. A product with no reviews has no rating, so that cell is empty in the CSV. pandas reads an empty cell as NaN. A few tools find and handle the gaps:
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") print(catalog["rating"].isna().sum(), "products have no rating") print(catalog[catalog["rating"].isna()][["sku", "name", "reviews"]]) rated = catalog.dropna(subset=["rating"]) print(len(rated), "rated products") print(catalog.loc[0, "name"]) # row label 0, column "name" print(catalog.iloc[-1]["sku"]) # the last row, by position
isna()givesTruefor each missing value, and.sum()counts theTruevalues.dropna(subset=["rating"])keeps only the rows that have a rating.fillna(0)would fill the gaps with 0. Think before you do that: a missing rating isn't a rating of zero, and filling it in would drag every average down.
.loc picks by label: the index value and the column name. .iloc picks by position, counting from 0, so iloc[-1] is the last row. They agree until you sort or filter a table. Then the labels keep their old values, but the positions start again from 0.
A week of trail temperatures
A ranger logged the noon temperature (°F) at two trailheads for a week. Set warmest_day to the day with the highest ridge reading (use idxmax), ridge_mean to the ridge's mean temperature rounded to 1 place, and colder_days to the number of days the ridge was colder than the valley.
import pandas as pd temps = pd.DataFrame({ "day": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], "ridge": [48, 52, 57, 55, 44, 61, 59], "valley": [55, 54, 60, 58, 51, 59, 62], }).set_index("day") warmest_day = None ridge_mean = None colder_days = None
Summarize by category
From the catalog, set counts to how many products each category has (a dictionary, from value_counts() then .to_dict()), and avg_rating to each category's mean rating, rounded to 2 places, as a dictionary.
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") counts = {} avg_rating = {}
Worked example: which categories grew?
The catalog has units sold in two quarters. Which categories sold more in the second?
import pandas as pd catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv") by_category = catalog.groupby("category")[["units_q1", "units_q2"]].sum() by_category["growth_pct"] = ((by_category["units_q2"] / by_category["units_q1"] - 1) * 100).round(1) print(by_category.sort_values("growth_pct", ascending=False))
groupby with a list of two columns sums both at once, giving one row per category. The growth is the second quarter divided by the first, minus 1, as a percentage. Sorting puts the fastest-growing category first. Five lines of pandas replace what would be a pivot table, a helper column and a sort in a spreadsheet, and they'll run unchanged on next quarter's file.
Saving results, and a warning about loops
to_csv("summary.csv", index=False) writes a DataFrame back to a file; index=False leaves out the row labels, which you rarely want in an export. to_dict(orient="records") turns it into a list of dictionaries, the shape JSON and APIs use.
You can loop over the rows of a DataFrame, but it's slow and long-winded, and almost never needed. When you feel the urge to write for row in ..., look for the column operation instead: a mask, a calculated column, a groupby. That habit is most of what it means to be fluent in pandas.
Practice what you just read
- 9 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
Which line keeps the rows where price is over 50 and stock is over 0?