Free Study Material
NumPy, from Class 11, gave you fast numeric arrays. This unit introduces Pandas — a library built on top of that same idea, but specifically for labelled, real-world, table-like data: exactly what a spreadsheet holds.
Pandas is a Python library for handling structured data, brought in with:
import pandas as pd
Its simplest structure is the Series — a one-dimensional, labelled array. Every element has both a value and an index label, unlike a plain NumPy array, which only has position.
pd.Series(data)
creates a Series from a list; Pandas assigns a default integer index (0, 1, 2, ...) unless you supply your own.
import pandas as pd
marks = pd.Series([78, 85, 92, 67])
print(marks)
named_marks = pd.Series([78, 85, 92], index=["Physics", "Chemistry", "Maths"])
print(named_marks)
marks prints each value alongside its default integer index, 0 through 3, one per line. named_marks uses index=["Physics", "Chemistry", "Maths"] to give each value a meaningful label instead — the same three scores, now reachable by subject name rather than only by position.
Note: A Series is genuinely labelled data, not just a list with extra formatting — that index travels with every value through slicing, filtering, and sorting, as the rest of this chapter shows.
Pandas (import pandas as pd) is a library for labelled, structured data. Its core one-dimensional structure is the Series — created with pd.Series(data), optionally with a custom index= — every value has both a position and a label.
Pandas (Python Data Analysis Library) is the most important tool in your Class 12 syllabus. It is built on top of NumPy but offers a major upgrade: Labeled Data.
| Feature | NumPy Array | Pandas Series |
|---|---|---|
| Indexing | Hidden (0, 1, 2...) | Explicit ('Jan', 'Feb'...) |
| Data Type | Homogeneous (Same type) | Heterogeneous (Mixed types) |
A Series is created using the pd.Series() constructor.
[0, 1, 2...].float).Run the code block below immediately.
This installs the Pandas library. If you skip this, none of the code examples in this chapter will work.
import sys
# 1. Force-load the micropip installer if it's missing
if "micropip" not in sys.modules:
import pyodide_js
await pyodide_js.loadPackage("micropip")
import micropip
# 2. Now use it to install Pandas and NumPy
print(" Installing Pandas... this may take a moment.")
await micropip.install(['pandas', 'numpy'])
print("System Ready: Pandas and NumPy have been installed!")
import pandas as pd
# --- Case 1: Default Index ---
# Pandas automatically assigns index 0, 1, 2
s1 = pd.Series(data=[10, 20, 30])
print("Default Indexing:")
print(s1)
# --- Case 2: Custom Index ---
# We assign labels 'A', 'B', 'C' to the data
s2 = pd.Series(data=[10, 20, 30], index=['A', 'B', 'C'])
print("Custom Indexing:")
print(s2)
This is a smart shortcut. The Dictionary Keys automatically become the Index, and Values become the Data.
If you provide a single value (e.g., 50) but multiple indices, Pandas Broadcasts (copies) that value to fill every row. This is useful for setting default values.
import pandas as pd
# --- Case 3: From Dictionary ---
# Keys ('India', 'UK') become the Index
capital_dict = {'India': 'New Delhi', 'UK': 'London', 'Japan': 'Tokyo'}
s_dict = pd.Series(capital_dict)
print("Dictionary Series:")
print(s_dict)
# --- Case 4: From Scalar (Broadcasting) ---
# The value 100 is repeated for all 3 rows
s_scalar = pd.Series(100, index=['Math', 'English', 'Science'])
print("Scalar Series:")
print(s_scalar)
This topic covers the properties every Series carries, and how arithmetic behaves on one — noticeably differently from a plain Python list.
A math operation on a Series applies to every element at once — no loop needed.
import pandas as pd
marks = pd.Series([78, 85, 92], index=["Physics", "Chemistry", "Maths"])
print(marks.index)
print(marks.size)
print(marks.dtype)
bonus_marks = marks + 5
print(bonus_marks)
marks.index prints the three subject labels; marks.size is 3. marks + 5 adds 5 to every value at once — Physics becomes 83, Chemistry 90, Maths 97 — with the same three labels carried over unchanged onto bonus_marks.
Note:marks + 5on a Series is nothing likesome_list + 5on a plain Python list — that would raise aTypeError, since+on a list means concatenation, not element-wise addition. This "apply to every element at once" behaviour is specific to Pandas (and NumPy) structures.
This element-wise behaviour is exactly what makes Pandas fast for real data work: adjusting every student's marks by a fixed grace amount is one line, marks + 5, instead of a hand-written loop over each one individually.
A Series carries index, values, size, and dtype. Arithmetic on a Series applies to every element at once, keeping the same index — genuinely different from + on a plain Python list, which means concatenation, not element-wise math.
Status Check: If you just logged in, click the Run button below to load the Pandas library.
(If you have already run this in a previous topic today, you can skip it.)
import sys
# SMART CHECK: Only install if missing
if "pandas" not in sys.modules:
print(" Toolkit: Initializing Python Environment...")
import pyodide_js
await pyodide_js.loadPackage("micropip")
import micropip
await micropip.install(['pandas', 'numpy'])
print(" Toolkit: Pandas & NumPy installed. You are ready to code!")
else:
print(" Toolkit: System is already ready. Proceed to the lesson.")
You created a Series, but now you need to ask it questions. "How big are you?" "What are your labels?"
We use Attributes to get these answers.
⚠️ Important Syntax Rule:
Attributes are properties, NOT functions. They do NOT use parentheses () at the end.
import pandas as pd
# Creating a Series of Student Marks
marks = pd.Series([95, 85, 75], index=['Amit', 'Sumit', 'Ravi'])
print("Sample Series Created:")
print(marks)
# 1. Check the Labels (Index)
print("Index Labels:", marks.index)
# 2. Check the Data (Values)
print("Raw Values:", marks.values)
# 3. Check Size
print("Total Elements:", marks.size)
# 4. Check Data Type
print("Data Type:", marks.dtype)
This is a Pandas superpower. You don't need loops (for i in list...) to do math.
If you perform an operation on a Series, it happens to Every Single Element at once. This is called Vectorization.
# Recall original marks: 95, 85, 75
# Add 5 to the ENTIRE Series at once
new_marks = marks + 5
print("Marks after Bonus:")
print(new_marks)
# Check condition
toppers = marks > 90
print("Who scored > 90?")
print(toppers)
# Output will be True/False for each student
A real Series can hold far more values than fit comfortably on screen. This topic covers previewing a large Series, and reaching one specific value.
n elements (default 5 if n is left out).n elements.series[label], or series.loc[label].series[position], or series.iloc[position].import pandas as pd
marks = pd.Series([78, 85, 92, 67, 74], index=["Physics", "Chemistry", "Maths", "English", "Hindi"])
print(marks.head(2))
print(marks.tail(2))
print(marks["Maths"])
print(marks.iloc[2])
marks.head(2) returns the first two entries (Physics, Chemistry). marks.tail(2) returns the last two (English, Hindi). marks["Maths"] reaches the value by its label, giving 92. marks.iloc[2] reaches the value at position 2 (the third element, zero-indexed) — also 92, since Maths happens to sit at that position.
Note:.loc[]always selects by label;.iloc[]always selects by integer position — the "i" inilocis a reminder for "integer." Using the wrong one for what you actually mean is a common, easily-avoided mistake.
head()/tail() exist for exactly the situation a real dataset creates: with thousands of rows, printing everything is neither useful nor readable — a quick preview of the start or end is what you actually need most of the time.
head(n)/tail(n) preview the first/last n elements. A value can be reached by label (series[label] or .loc[label]) or by position (series[position] or .iloc[position]) — .loc is always label-based, .iloc is always position-based.
Status Check: If you just logged in, click the Run button below to load the Pandas library.
(If you have already run this in a previous topic today, you can skip it.)
import sys
# SMART CHECK: Only install if missing
if "pandas" not in sys.modules:
print(" Toolkit: Initializing Python Environment...")
import pyodide_js
await pyodide_js.loadPackage("micropip")
import micropip
await micropip.install(['pandas', 'numpy'])
print(" Toolkit: Pandas & NumPy installed. You are ready to code!")
else:
print(" Toolkit: System is already ready. Proceed to the lesson.")
Real-world data often has thousands of rows. Printing the whole thing will flood your screen.
We use head() and tail() to quickly inspect the "Top" and "Bottom" of the data.
import pandas as pd
# Create a Series with numbers 0 to 19 (Total 20 items)
nums = pd.Series(range(20))
# 1. Default Head (Shows Top 5: 0 to 4)
print("--- Top 5 Rows ---")
print(nums.head())
# 2. Custom Tail (Shows Bottom 3: 17, 18, 19)
print("\n--- Bottom 3 Rows ---")
print(nums.tail(3))
You can slice a Series just like a list using [ start : stop ]. However, the rules change depending on whether you use Numbers or Labels.
[1:3] gives indices 1 and 2.['Jan':'Mar'] gives Jan, Feb, AND Mar.import pandas as pd
# Data: ['A', 'B', 'C', 'D', 'E']
# Index: [0, 1, 2, 3, 4]
s = pd.Series(['A', 'B', 'C', 'D', 'E'])
print("Original Values:", s.values)
# Slice [1:3] -> Start at 1, Stop BEFORE 3
# Expect: Index 1 ('B') and Index 2 ('C')
print("\nSlice [1:3] Result:")
print(s[1:3])
import pandas as pd
# Creating Series with Custom Index
s_label = pd.Series([10, 20, 30, 40], index=['Jan', 'Feb', 'Mar', 'Apr'])
# Slice from 'Jan' to 'Mar'
# Since we use LABELS, 'Mar' IS included.
print("Label Slice ['Jan':'Mar']:")
print(s_label['Jan':'Mar'])
This topic covers pulling out only the values that satisfy a condition — one of the most common real operations on a Series.
A comparison on a Series (series > value) produces a new Series of True/False values, one per element. Passing that Boolean Series back inside [ ] returns only the elements where it was True — this pattern is called Boolean Indexing.
import pandas as pd
marks = pd.Series([78, 85, 92, 67, 74], index=["Physics", "Chemistry", "Maths", "English", "Hindi"])
print(marks > 75)
print(marks[marks > 75])
marks > 75 produces a Series of the same five labels, each now holding True or False depending on whether that subject's mark exceeds 75. marks[marks > 75] then uses that Boolean Series to filter marks itself, returning only Physics, Chemistry, and Maths — the three subjects that were actually above 75.
Note:marks[marks > 75]is doing two steps at once, read from the inside out: firstmarks > 75builds the True/False Series, then the outer[ ]uses it to filter — this two-step reading is worth tracing explicitly the first few times.
Boolean Indexing is the Pandas equivalent of a Python for/if loop collecting matching values into a new list — except it filters the entire Series at once, with no explicit loop written at all, exactly the same "operate on everything together" philosophy as the last topic's arithmetic.
A comparison on a Series (series > value) produces a Boolean Series; passing that back inside [ ] (series[series > value]) filters to only the matching elements — Boolean Indexing, the standard way to filter a Series without writing an explicit loop.
Status Check: If you just logged in, click the Run button below to load the Pandas library.
(If you have already run this in a previous topic today, you can skip it.)
import sys
# SMART CHECK: Only install if missing
if "pandas" not in sys.modules:
print(" Toolkit: Initializing Python Environment...")
import pyodide_js
await pyodide_js.loadPackage("micropip")
import micropip
await micropip.install(['pandas', 'numpy'])
print(" Toolkit: Pandas & NumPy installed. You are ready to code!")
else:
print(" Toolkit: System is already ready. Proceed to the lesson.")
Filtering is like asking your data a question.
"Who scored more than 80?"
"Which days had temperature > 40?"
In Pandas, this is a 2-step process:
import pandas as pd
# Data: Marks of 4 students
marks = pd.Series([95, 45, 88, 32], index=['Amit', 'Ben', 'Cat', 'Dan'])
# Step 1: Ask the question "Who passed (> 40)?"
mask = marks > 40
print("The Mask (True/False):")
print(mask)
# Step 2: Pass the mask inside the Series
passed_students = marks[mask]
print("Final Filtered List:")
print(passed_students)
import pandas as pd
s = pd.Series([10, 15, 20, 25])
# One-line filtering: Show numbers divisible by 2 (Even numbers)
evens = s[ s % 2 == 0 ]
print("Even Numbers:")
print(evens)
This closing topic of the chapter covers putting a Series in order — either by its values, or by its labels.
Both accept ascending=False for descending order, and both return a new Series by default, leaving the original unchanged.
import pandas as pd
marks = pd.Series([78, 85, 92, 67], index=["Physics", "Chemistry", "Maths", "English"])
print(marks.sort_values())
print(marks.sort_values(ascending=False))
print(marks.sort_index())
marks.sort_values() reorders the entries by score, ascending: English (67), Physics (78), Chemistry (85), Maths (92) — each value keeps its own original label attached as it moves. marks.sort_values(ascending=False) gives the same four entries in the opposite order. marks.sort_index() instead reorders alphabetically by subject name: Chemistry, English, Maths, Physics — the scores travel with their labels, not with their original position.
Note: Both sorting methods return a new Series by default —marksitself stays in its original order unless you explicitly reassign it,marks = marks.sort_values().
A label always stays attached to its own value through sorting, exactly the same guarantee Boolean Indexing relied on in the last topic — a Series' index isn't cosmetic, it's data that travels with its value through every operation in this chapter.
sort_values() orders a Series by its data; sort_index() orders it by its labels — both accept ascending=False and both return a new, reordered Series by default, with every value's own label carried along with it.
Status Check: If you just logged in, click the Run button below to load the Pandas library.
(If you have already run this in a previous topic today, you can skip it.)
import sys
# SMART CHECK: Only install if missing
if "pandas" not in sys.modules:
print("Toolkit: Initializing Python Environment...")
import pyodide_js
await pyodide_js.loadPackage("micropip")
import micropip
await micropip.install(['pandas', 'numpy'])
print("Toolkit: Pandas & NumPy installed. You are ready to code!")
else:
print("Toolkit: System is already ready. Proceed to the lesson.")
Data is rarely entered in perfect order. You often need to rearrange it (e.g., finding the highest score or the lowest price).
We use the .sort_values() method.
import pandas as pd
# Random scores
scores = pd.Series([45, 12, 88, 32, 99])
print("Original Data:")
print(scores.values)
# Sort Low -> High (Default)
sorted_scores = scores.sort_values()
print("\nSorted Data (Ascending):")
print(sorted_scores.values)
# Sort High -> Low
toppers = scores.sort_values(ascending=False)
print("Top Scores (Descending):")
print(toppers.values)
Sometimes your data is jumbled, but you want to organize it alphabetically by Name (Row Label).
We use the .sort_index() method.
Example: If your rows are ['Zebra', 'Apple', 'Cat'], this will rearrange them to ['Apple', 'Cat', 'Zebra'].
import pandas as pd
# Data: Dictionary with jumbled keys
data = {'Zebra': 10, 'Apple': 50, 'Cat': 30}
s = pd.Series(data)
print("Original (Jumbled):")
print(s)
# Sort Alphabetically by Index (A-Z)
s_sorted = s.sort_index()
print("\nSorted by Index:")
print(s_sorted)