You already used Series without knowing it — every time you wrote df['price'], pandas handed you one. Today you meet the Series head-on, and you learn the one idea that explains why two columns sometimes add up wrong: the Index. Get this, and pandas stops surprising you.
What you'll learn
What
A pandas Series is a 1-dimensional labeled array: a sequence of values paired with an Index of labels. A DataFrame is really just a set of Series that share one common Index.
Why
The Index is what makes pandas different from a plain list or NumPy array. It powers label-based lookup, automatic alignment when you combine data, and the join keys you'll rely on later. Misunderstand it, and you get silent NaNs and mismatched rows.
Where it's used
Every column you select, every value_counts() result, every groupby aggregation, and every merge key is a Series with an Index. Alignment by Index runs underneath almost every pandas operation.
Where this runs in production
A Series indexed by track_id maps each song to its play count. Because the Index is the track_id, Spotify can add this month's counts to last month's and pandas aligns each track automatically — even if the two months contain different songs.
A stock's closing prices live in a Series indexed by date. The date Index lets analysts select a range, compute daily returns, and align two tickers that traded on different days without manual row matching.
Ratings stored in a Series indexed by restaurant name allow instant label-based lookup — series['Bombay Canteen'] — instead of scanning a list to find the right row.
Each hook has a numbered tag (the Index label) and holds one coat (the value). You don't count along the rack to find your coat — you hand over your tag and get the exact hook. A Series works the same way: the Index label retrieves the value directly, no counting required.
import pandas as pd
# From a list — pandas builds a default 0,1,2 index
sales = pd.Series([250, 180, 320], name='daily_sales')
print(sales)
# From a dict — keys become the index labels
prices = pd.Series({'apple': 0.5, 'banana': 0.3, 'cherry': 2.0})
print(prices)With a list, pandas assigns a default integer Index (0, 1, 2). With a dictionary, the keys become the Index labels and the values become the data. The optional name becomes the Series' name — which becomes the column header when the Series joins a DataFrame.
series.values → the raw NumPy array of data. series.index → the labels. series.dtype → the type of the values. Keeping these three separate in your head is the whole lesson.
prices = pd.Series({'apple': 0.5, 'banana': 0.3, 'cherry': 2.0})
print(prices['banana']) # label lookup -> 0.3
print(prices.iloc[1]) # position lookup -> 0.3
print(prices.index) # Index(['apple','banana','cherry'], dtype='object')Here label and position happen to agree, but they are different operations. The moment you sort, filter, or reindex, positions shift while labels stick to their values.
If you add a Series indexed ['a','b','c'] to one indexed ['b','c','d'], you get labels a, b, c, d — and a and d become NaN because each appears in only one Series. pandas will not warn you. Always check that two Series share the same Index before arithmetic, or reset the index if you truly want positional math.
Click each part to see how the Index, values, and dtype combine into a Series — and scale up into a DataFrame.
You're tracking three days of website signups and want the values and labels separately.
A Series from a list. pandas attaches a default integer Index: 0, 1, 2. The values are 42, 58, 37.
Your task
A subscription business tracks active subscribers per plan. Two months of counts are given as Series indexed by plan name. Complete the tasks so the summary prints correctly.
Pro in Feb: 850 Team total: 300 Top Feb plan: basic
Write your solution in the editor on the right, then hit Run.
What are the two components of a pandas Series?
You build `s = pd.Series({'a': 1, 'b': 2})`. What does `s['b']` return?
Adding `pd.Series({'x':1,'y':2})` to `pd.Series({'y':3,'z':4})` produces which result for label `x`?
What is the correct fix?
You are given a Series of product ratings indexed by product name. Print the name of the highest-rated product and its rating, formatted as 'name: rating'. ratings = pd.Series({'Echo': 4.2, 'Kindle': 4.6, 'FireTV': 4.1, 'Ring': 4.4})
After `s = pd.Series([10,20,30], index=['a','b','c'])`, which pair of expressions both return 20?
What is a pandas Series, and how does it differ from a Python list?
A Series is a 1-dimensional labeled array: values paired with an Index. Compared to a Python list, it adds three things. First, an Index, so I can look values up by meaningful labels instead of only by position. Second, a single dtype and a NumPy-backed store, so vectorized math runs in fast compiled code. Third, automatic alignment, so when I combine two Series pandas matches them by label. A list has none of that — it's just an ordered sequence I have to index by integer and loop over in Python.
Explain index alignment and give an example of a bug it can cause.
When you do arithmetic on two Series, pandas lines them up by Index label rather than by position, and any label present in only one Series becomes NaN in the result. This is usually helpful — it means I can add this month's and last month's metrics per customer without manually matching rows. But it bites when I assume positional behaviour. For example, if I compute df['a'] - other_series expecting element-by-element subtraction, but other_series has a different or reordered index, I get NaNs and shifted values instead of an error. The fix is to make the indexes match deliberately — reset_index, reindex, or use .values to force positional math when that's what I actually want.
How does a Series relate to a DataFrame internally, and why does that matter for performance and memory?
A DataFrame is essentially an ordered collection of Series that share a single row Index, with the columns typically stored in a block manager that groups same-dtype columns into contiguous 2D arrays. That shared Index is why selecting a column is cheap — it's a view onto existing data, not a copy — and why alignment is consistent across every column. For performance it means operations stay vectorized as long as I work column-wise on homogeneous dtypes; the moment a column is object dtype or I iterate rows, I drop out of the fast C path. For memory it means dtype choice matters a lot: downcasting a float64 column to float32, or converting a low-cardinality string column to category, can cut a Series' footprint dramatically, and because each column is its own Series I can optimise them independently.
Common Mistakes to Avoid
1) Assuming Series arithmetic is positional — it aligns by Index label, so mismatched indexes give NaN, not element-by-element results. 2) Confusing s['b'] (label) with s.iloc[1] (position); they agree only while the index is the default range. 3) Forgetting that a filtered DataFrame's column keeps the original row labels — use .reset_index(drop=True) when you need clean 0-based positions. 4) Using the + operator when you need missing labels treated as zero; reach for s1.add(s2, fill_value=0) instead. 5) Expecting a mixed-type Series to be numeric — one string value forces the whole Series to object dtype and breaks arithmetic.
Ask the AI Tutor
Try these prompts in the AI Tutor panel: • 'ELI5: what is a Series Index and why does it matter?' • 'Show me a bug caused by index alignment and how to fix it.' • 'Quiz me on label lookup versus positional lookup.' • 'Explain fill_value in Series.add with a fresh example.' • 'Interview mode: ask me to explain how a Series relates to a DataFrame.'
Glossary
Series — a 1D labeled array of values plus an Index. Index — the labels attached to a Series' values; drives lookup and alignment. values — the underlying NumPy array of data. dtype — the single data type of a Series' values. name — an optional label for the whole Series; becomes a column header in a DataFrame. Alignment — pandas matching two Series by Index label during operations. fill_value — an argument to methods like .add() that substitutes a value where a label is missing. pct_change() — element-over-previous percentage change in Index order. idxmax() — returns the Index label of the maximum value.
Recommended Resources
• Docs: the pandas 'Intro to data structures' guide — the official Series section, straight from the source. • Read: 'Index objects' in the pandas API reference to see everything an Index can do. • Practice: build two Series with partly overlapping string indexes, add them with + and with .add(fill_value=0), and compare the results until alignment feels predictable. • Next in DSM: you can address values by label — next, in Data Selection, you'll wield .loc, .iloc, and boolean masks to pull exactly the rows and columns you want from a full DataFrame.
Recap
✓ A Series is a 1D labeled array: values paired with an Index. ✓ series.values, series.index, and series.dtype are three separate things worth inspecting. ✓ Label lookup (s['b']) uses the Index; positional lookup (s.iloc[1]) counts from zero. ✓ Series arithmetic aligns by Index label — labels in only one Series become NaN. ✓ Use s1.add(s2, fill_value=0) to treat a missing label as zero instead of NaN. ✓ A DataFrame is a set of Series sharing one Index, which is why columns keep their row labels. Next up: Data Selection: loc, iloc & Boolean Masking. You can address a single Series by label — next you'll select any rows and columns from a full DataFrame with precision, and sidestep the chained-indexing trap that quietly corrupts data.
Run your code to see the output here.