🐼 Section 2 · Pandas 🟢 Beginner MODULE 08

Pandas Series and DataFrames

⏱️ 26 min read
📖 Theory + Code
🧩 5 Quiz Questions
🏗️ 1 Challenge
Your progress in Section 217%
🎯 What you'll learn: What Pandas is and why it's built directly on top of NumPy, how to create a Series from a list or a dict, how to create a DataFrame from a dict of lists, the inspection methods .head(), .tail(), .info(), and .describe(), checking a DataFrame's .shape, the difference between df['col'] and df.col, and the basics of .loc/.iloc selection.

What Is Pandas, and Why Is It Built on NumPy?

Pandas is a Python library for working with labeled, tabular data — the kind of data you'd normally put in a spreadsheet or a database table: rows and columns, where every column can have its own name and its own data type. It's the single most-used tool in the Python data science stack, and it sits directly on top of NumPy.

Every column inside a Pandas table is, under the hood, a NumPy array. That's not a coincidence — Pandas was built specifically to add row and column labels, mixed column types, and a huge library of data-wrangling methods (filtering, grouping, merging, reshaping) on top of the fast, vectorized foundation NumPy already provides. Anything you learned about vectorized math and boolean masking in Section 1 carries over directly.

📇
Series
A single labeled column of data — like one column from a spreadsheet, plus an index.
📊
DataFrame
A 2D table made of multiple Series sharing the same index — the workhorse of Pandas.
📝
The standard import
By near-universal convention, Pandas is imported as pd: import pandas as pd. You'll see pd. in front of almost every function for the rest of this course, the same way np. prefixed every NumPy call in Section 1.

Creating a Series

A Series is a one-dimensional, labeled array. Every value has an associated index label — by default, integers starting at 0, but you can supply your own.

creating_series.py
PYTHON
import pandas as pd

# From a plain list — Pandas assigns a default integer index (0, 1, 2, ...)
prices = pd.Series([19.99, 24.50, 7.25, 42.00])
print(prices)
# 0    19.99
# 1    24.50
# 2     7.25
# 3    42.00
# dtype: float64

# From a dict — the dict keys become the index labels automatically
inventory = pd.Series({"apples": 50, "bananas": 30, "pears": 12})
print(inventory["bananas"])   # 30 — label-based lookup
print(inventory.index)      # Index(['apples', 'bananas', 'pears'], dtype='object')
print(inventory.values)     # array([50, 30, 12]) — the underlying NumPy array
A Series is a NumPy array with labels
.values hands you back the plain NumPy array sitting underneath a Series. Everything you know about vectorized math still applies — prices * 0.9 works exactly like it did on a NumPy array in Section 1, but now the result keeps its index labels too.

Creating a DataFrame

The most common way to build a small DataFrame by hand is from a dict of lists — each key becomes a column name, and each list becomes that column's values. Every list must be the same length.

creating_dataframe.py
PYTHON
import pandas as pd

data = {
    "name": ["Aiman", "Bilal", "Chen", "Deepa"],
    "age": [23, 31, 27, 35],
    "city": ["Lahore", "Karachi", "Beijing", "Delhi"],
    "score": [88.5, 92.0, 76.5, 99.0],
}

df = pd.DataFrame(data)
print(df)
#     name  age     city  score
# 0  Aiman   23   Lahore   88.5
# 1  Bilal   31  Karachi   92.0
# 2   Chen   27  Beijing   76.5
# 3  Deepa   35    Delhi   99.0
df — 4 rows × 4 columns
nameagecityscore
0Aiman23Lahore88.5
1Bilal31Karachi92.0
2Chen27Beijing76.5
3Deepa35Delhi99.0
📝
A DataFrame is a dict of Series
Conceptually, a DataFrame is a collection of Series objects that all share the same index — one Series per column. That's why df['age'] hands you back a Series, and why every column keeps its own dtype even though they all live in the same table.

First Look: head, tail, info, describe, shape

Before doing anything else with a new DataFrame, get in the habit of running these five checks. They tell you what you're actually working with.

first_look.py
PYTHON
# df is the 4-row DataFrame from Section 3

df.head()       # first 5 rows (fewer if the DataFrame is smaller) — default n=5
df.head(2)    # first 2 rows only
df.tail(2)    # last 2 rows

print(df.shape)   # (4, 4) — (rows, columns), same idea as a NumPy array's .shape

df.info()       # column names, dtypes, non-null counts, and memory usage
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 4 entries, 0 to 3
# Data columns (total 4 columns):
#  #   Column  Non-Null Count  Dtype
# ---  ------  --------------  -----
#  0   name    4 non-null      object
#  1   age     4 non-null      int64
#  2   city    4 non-null      object
#  3   score   4 non-null      float64

df.describe()   # summary statistics for numeric columns only: count, mean, std, min, quartiles, max
⚠️
info() vs. describe() — different jobs
.info() tells you about structure — dtypes and how many values are missing in each column. .describe() tells you about distribution — mean, spread, and range — and only looks at numeric columns by default. Run both, every time, on any dataset you haven't seen before.

Selecting Columns and Rows: bracket, dot, loc, iloc

There are several ways to pull data out of a DataFrame, and each one has a slightly different job.

selecting_data.py
PYTHON
# --- Column selection ---
print(df["name"])     # bracket notation — always works, even with spaces in the name
print(df.name)        # dot notation — same result, but breaks if the column name isn't a valid identifier
print(df[["name", "score"]])   # double brackets = a DataFrame with multiple columns

# --- .loc — select by LABEL ---
print(df.loc[0])              # row with index label 0, as a Series
print(df.loc[0, "city"])      # single value: row 0, column "city"
print(df.loc[0:2, ["name", "age"]])  # rows 0 through 2 (INCLUSIVE), two columns

# --- .iloc — select by INTEGER POSITION ---
print(df.iloc[0])              # first row by position, regardless of its label
print(df.iloc[0, 2])           # row at position 0, column at position 2
print(df.iloc[0:2])            # rows at positions 0 and 1 — stop is EXCLUSIVE, like Python slicing
loc is label-based, iloc is position-based
The easiest way to remember the difference: .loc uses the labels you can see in the index/columns, and its slices include the endpoint. .iloc uses plain integer positions, counting from 0, and its slices exclude the endpoint — just like a Python list. When the index is the default 0,1,2,..., .loc[0:2] and .iloc[0:2] can look deceptively similar but return different numbers of rows.

Lesson Summary

Let's recap everything you learned in this lesson:

Pandas adds labeled, tabular structure on top of NumPy — every DataFrame column is a NumPy array underneath.
A Series is one labeled column; a DataFrame is a 2D table of Series sharing an index.
Build a DataFrame from a dict of lists with pd.DataFrame(data).
.head(), .tail(), .info(), .describe(), and .shape are your first-look toolkit for any new DataFrame.
.loc selects by label (inclusive slices); .iloc selects by integer position (exclusive slices).
🧩 Knowledge Check — Lesson 8
Answer all 5 questions to test your understanding. Instant feedback on every answer.
1. What is a Pandas DataFrame column, underneath?
2. Which line builds a DataFrame from a dict of lists?
3. Which method shows column dtypes and non-null counts?
4. What's true about df.loc[0:2] versus df.iloc[0:2] on a default integer index?
5. Why might df.col_name (dot notation) fail when df['col_name'] works fine?
💪
Coding Challenge — Lesson 8
Apply what you learned · Beginner Level

Build and inspect a small DataFrame of your own.

Challenge: Product Catalog 🛒

Build a dict with three keys — "product" (5 product name strings), "price" (5 floats), and "in_stock" (5 booleans) — and turn it into a DataFrame called catalog. Then: (1) print catalog.shape, (2) print catalog.head(3), (3) print just the "price" column using bracket notation, and (4) use .loc to print the "product" and "price" columns for the first two rows only.

Rules: Use a dict of lists with pd.DataFrame() — don't build the rows one at a time.
💡 Show hints if you're stuck
  • Build the dict: data = {"product": [...], "price": [...], "in_stock": [...]}
  • Create the frame: catalog = pd.DataFrame(data)
  • First two rows, two columns: catalog.loc[0:1, ["product", "price"]]
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 8 Complete!

You can create Series and DataFrames, inspect them, and select data with loc/iloc. Next up: reading real CSV files and cleaning messy data.

Module 08 of 13 Section 2 — Pandas: Data Analysis Powerhouse