Section 2 · pandas Foundations
Series and DataFrames
16 min3 min readPython exercise
Lesson at a glance
Build pandas Series and DataFrames from Python lists and dictionaries, run calculations on whole columns at once, and predict how pandas lines values up by their index labels.
- 1Series: a column with labels
- 2Calculations on every value at once
- 3Alignment: pandas matches labels, not positions
- 4DataFrame: a table of Series
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
In the last section’s exercise, month names lived in one list and revenues in another, and you had to find the best month by its position and then look up the name in the other list. Sort one list and forget the other, and every month gets the wrong revenue without any error. pandas exists to make that kind of mistake hard: values carry their labels with them.
Series: a column with labels
A Series is a one-dimensional array of values plus an index: one label per value.
import pandas as pd
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
revenue = pd.Series(
[16167, 14906, 18208, 18817, 17428, 13933,
22968, 32042, 25514, 30339, 75334, 41153],
index=months,
name="revenue_2025",
)
print(revenue.head(3))
print(revenue["Nov"], revenue.idxmax(), revenue.sum())import pandas as pd loads the library under its universal nickname; every pandas tutorial and Stack Overflow answer uses pd. revenue["Nov"] looks a value up by label, and idxmax() returns the label of the largest value, which is the whole parallel-lists dance from Section 1 in one call.
The last line of the printed Series reads Name: revenue_2025, dtype: int64. The dtype is the type of every value in the column: int64 for whole numbers, float64 for decimals, object (or str in pandas 3) for text, bool, and datetime64 for dates. One column, one type; that rule is what makes pandas fast.
Calculations on every value at once
Arithmetic and comparisons on a Series apply to each value and return a new Series with the same index. This is called vectorised code: you describe the operation on the whole column and pandas does the loop in fast compiled code.
import pandas as pd
revenue = pd.Series([30339, 75334, 41153], index=["Oct", "Nov", "Dec"])
print(revenue * 1.05) # every month, 5% higher
print(revenue / revenue.sum()) # each month's share of Q4
print(revenue > 40000) # a Series of True/FalseThe True/False Series on the last line looks like a curiosity now. In Lesson 2.4 it becomes the main way you filter rows.
Alignment: pandas matches labels, not positions
When you combine two Series, pandas lines them up by index label before it calculates. Here is Q4 2025 against Q4 2024, with the months deliberately written in a different order and one month missing:
import pandas as pd
q4_2025 = pd.Series([30339, 75334, 41153], index=["Oct", "Nov", "Dec"])
q4_2024 = pd.Series([29177, 9360], index=["Nov", "Oct"])
print(q4_2025 - q4_2024)October is subtracted from October and November from November, regardless of order. December exists only in 2025, so its result is NaN (“not a number”), pandas’ marker for a missing value. Notice the result became float64: in classic NumPy-backed columns, NaN is a float, so an integer column that gains a missing value turns into floats.
DataFrame: a table of Series
A DataFrame is several Series side by side, sharing one index. The easiest way to build a small one is the dictionary-of-lists shape you met in Lesson 1.3, one key per column:
import pandas as pd
products = pd.DataFrame({
"name": ["Cast Iron Skillet 26cm", "Burr Coffee Grinder", "Standing Desk 140cm", "Cork Yoga Mat"],
"unit_price": [39, 129, 449, 72],
"unit_cost": [17.45, 76.99, 204.98, 41.52],
})
print(products)
print(products.shape) # (rows, columns)
print(products.dtypes)Three attributes describe any DataFrame, and you will print them constantly. shape is the (rows, columns) pair, columns lists the column names, and index holds the row labels. None of them has parentheses, because they are stored facts about the table rather than actions; head() and sum() do work, so they are called with parentheses. Mixing the two up gives either a method printed as <bound method ...> or a TypeError: 'tuple' object is not callable, both of which you can now recognise.
pandas gave the rows a default index, 0 to 3. Each column is a Series you can pull out with square brackets, and column arithmetic lines up row by row because the columns share that index:
import pandas as pd
products = pd.DataFrame({
"name": ["Cast Iron Skillet 26cm", "Burr Coffee Grinder", "Standing Desk 140cm", "Cork Yoga Mat"],
"unit_price": [39, 129, 449, 72],
"unit_cost": [17.45, 76.99, 204.98, 41.52],
})
margin = products["unit_price"] - products["unit_cost"]
print(margin)
print(type(products["unit_price"]))The skillet earns 21.55 dollars per unit and the standing desk 244.02. Four rows or four million, the code is the same, and that is the real promise of pandas: you think in columns, not in cells.
Building tables by hand is for learning. Next you load Cartwheel’s real CSV files and learn the inspection routine to run before you trust any of them.
Key takeaways
- A Series is a column of values with an index of labels; a DataFrame is a table of Series that share one index.
- Arithmetic on a Series applies to every value at once, so you write
prices * 1.05instead of a loop. - Operations between two Series line up by index label, not by position; labels present in only one side produce
NaN. df["col"]gives one column as a Series, anddf.shape,df.columns,df.indexanddf.dtypesdescribe the table.