PD-01-001-P · Primer — Series vs DataFrame
The idea in one sentence
A Series is a single labeled column of data, and a DataFrame is a table built from one or more Series that all share the same row labels.
Why it exists
A plain Python list can hold [18, 21, 19], but it can't tell you which day each number belongs to — you're stuck remembering that position 0 means Monday. Real data almost always comes with names attached: dates, cities, user IDs, product SKUs. pandas gives every value a label so you can ask for temps["Tue"] instead of temps[1] and never lose track of what a number means. The Series solves this for one column of labeled data; the DataFrame stacks many such columns side by side into a spreadsheet-like table where every column agrees on the same set of row labels. Once your data is labeled, pandas can automatically line up, filter, group, and join it for you — which is the whole point of the library.
How it works
A Series is a 1-D array of values plus an index (the labels) and an optional name. A DataFrame is a 2-D table: think of it as a dictionary of Series, where each column is a Series and all columns share one common row index. You build a DataFrame by passing a dict of columns; you pull a single column back out with df["col"], and — this is the key insight — that column is a Series. So the two objects aren't rivals; a DataFrame is literally made of Series. Both objects also carry an index, which is why label-based lookup works the same way in each. The example below builds one Series, then a two-column DataFrame over the same days, then shows that indexing a column hands you a Series right back.
import pandas as pd
# A Series: one labeled column of data
temps = pd.Series([18, 21, 19], index=["Mon", "Tue", "Wed"], name="temp_c")
print(temps)
print("look up by label:", temps["Tue"])
# A DataFrame: several aligned Series sharing one index
weather = pd.DataFrame(
{"temp_c": [18, 21, 19], "rain_mm": [2, 0, 5]},
index=["Mon", "Tue", "Wed"],
)
print(weather)
# Pulling one column back out gives a Series
print(type(weather["rain_mm"]).__name__)
# Expected output:
# Mon 18
# Tue 21
# Wed 19
# Name: temp_c, dtype: int64
# look up by label: 21
# temp_c rain_mm
# Mon 18 2
# Tue 21 0
# Wed 19 5
# Series
The one gotcha
df["col"] returns a Series (1-D), but df[["col"]] — with double brackets — returns a DataFrame with one column (2-D). Beginners mix these up constantly: single brackets give you one labeled column you can do math on, while double brackets give you a table that still has column headers. If a later method complains it "expected 2 dimensions" or a printout suddenly grows a header row, check your bracket count first.
See it — one Series becomes a column inside a DataFrame
flowchart LR A["Series 'temp_c'<br/>Mon 18 · Tue 21 · Wed 19"] --> C["DataFrame<br/>index: Mon Tue Wed"] B["Series 'rain_mm'<br/>Mon 2 · Tue 0 · Wed 5"] --> C C --> D["df['temp_c']<br/>returns a Series again"] C --> E["df[['temp_c']]<br/>returns a 1-col DataFrame"]
The one-line takeaway
A Series is one labeled column; a DataFrame is a table of Series sharing one index — and pulling a single column back out gives you a Series.
Where you'll meet it
Every pandas workflow starts here: reading a CSV gives you a DataFrame, selecting a column gives you a Series, and computing a mean, filtering rows, or grouping data all rest on knowing which of the two you're holding. You'll lean on this distinction constantly when cleaning data, building features for a model, or answering "what's the average per category?" in an interview.
Named concepts: Series, DataFrame, index, labeled data
