This chapter introduces Pandas, the Python library used to store and work with tabular data such as a school register, a shop's inventory or a list of student marks. It covers the two main Pandas data structures β the Series and the DataFrame β and how to create them, access and change their data, and add, delete and rename rows and columns.
Read slowly, run every code example in Python, and read the π― Board Exam Tips with each section. Pay particular attention to Series creation, DataFrame creation, loc/iloc and add/delete operations during revision.
1.1 π Introduction β What is Pandas?
Imagine you have 500 students' marks in a school. You need to:
- Find who scored above 90
- Calculate the class average
- Sort by marks
Doing this manually takes hours. Writing raw Python code takes many lines. Pandas does it in ONE line!
graph LR
PROB["π© Problem\nManaging big data\nin Python is painful"]
PANDAS["πΌ Pandas\nPowerful data tool\nbuilt on NumPy"]
SOL["π Solution\nAnalyse, filter, sort\nin seconds!"]
PROB --> PANDAS --> SOL
style PANDAS fill:#FF9800,color:#fff
style SOL fill:#4CAF50,color:#fff
What Pandas Does
Handles tables of data like Excel
But MUCH faster and works inside Python code!Built On
NumPy (the math engine)
Pandas = NumPy + labels + convenienceWho Uses It
Data Scientists, Companies, Researchers
GoogleAmazonISRO use Pandas daily!Why Fast
Vectorization β processes whole column at once
No slow loops neededBest Analogy: Think of Python lists as a hand-written notebook and Pandas as an Excel spreadsheet. Both store data, but Excel has built-in tools for filtering, sorting, and calculations. That's exactly what Pandas adds to Python!
1.2 π Using Pandas
Before you can use Pandas, you need to import it. We give it a short nickname pd so we don't type "pandas" every time.
# import_pandas.py
import pandas as pd # Standard practice β always use alias 'pd'
import numpy as np # Often imported together with pandas
print("Pandas version:", pd.__version__)
This is a universal convention β every Pandas programmer in the world writes import pandas as pd. If you write import pandas, your code still works but you'd type pandas.Series() instead of pd.Series(). Short = better!
"How do you import the Pandas library?" β 1-mark question.
Answer: import pandas as pd
If they ask for numpy too: import numpy as np
1.3 ποΈ Pandas Data Structures
Pandas gives us two main data structures to store and manage data:
graph TD
PANDAS["πΌ PANDAS\nData Structures"]
S["π Series\n1-Dimensional\nLike one column"]
D["π DataFrame\n2-Dimensional\nLike a full table"]
PANDAS --> S
PANDAS --> D
S --> EX1["Marks of ONE student\nacross subjects:\nMath: 85, Sci: 90, Eng: 78"]
D --> EX2["Marks of ALL students\nacross ALL subjects\n(full spreadsheet!)"]
style S fill:#2196F3,color:#fff
style D fill:#9C27B0,color:#fff
Series
1D β like a single column in Excel
Roll numbersmarks of one studentprices of itemsDataFrame
2D β like a complete Excel sheet
Full class resultproduct catalogueattendance register1.4 π Series β The Smart Column
A Series is a one-dimensional labeled array. It's like a Python list, but every element has a label (called index) attached to it.
Without Pandas (List): With Pandas (Series):
[85, 90, 78] 0 85
1 90
2 78
dtype: int64
The left column (0, 1, 2) = Index (label) The right column (85, 90, 78) = Values (data)
1.4.1 Creating Series Objects π οΈ
Method 1: From a List (most basic)
# series_from_list.py
import pandas as pd
marks = [85, 90, 78, 92, 65]
s = pd.Series(marks)
print(s)
Output:
0 85
1 90
2 78
3 92
4 65
dtype: int64
Pandas automatically assigns index 0, 1, 2, 3, 4 when you don't specify one.
Method 2: From a List WITH Custom Index
# series_custom_index.py
import pandas as pd
marks = [85, 90, 78]
students = ['Amit', 'Neha', 'Raj']
s = pd.Series(marks, index=students)
print(s)
Output:
Amit 85
Neha 90
Raj 78
dtype: int64
Now instead of 0, 1, 2 β we have names as labels! Much more readable!
Method 3: From a Dictionary (Keys β Index, Values β Data)
# series_from_dict.py
import pandas as pd
data = {'Math': 95, 'Science': 88, 'English': 72, 'Hindi': 80}
s = pd.Series(data)
print(s)
Output:
Math 95
Science 88
English 72
Hindi 80
dtype: int64
Dictionary keys automatically become the Index! This is the most natural way.
Method 4: From a Scalar (Same Value Everywhere)
# series_scalar.py
import pandas as pd
s = pd.Series(100, index=['A', 'B', 'C', 'D'])
print(s)
Output:
A 100
B 100
C 100
D 100
dtype: int64
Fills all positions with the same value β useful for creating blank/default data!
1.4.2 Creating Series Objects β Additional Functionality π§
Using dtype to specify data type:
# series_dtype.py
import pandas as pd
s = pd.Series([1, 2, 3], dtype=float)
print(s)
Output:
0 1.0
1 2.0
2 3.0
dtype: float64
Using name to give the Series a title:
# series_name.py
import pandas as pd
s = pd.Series([85, 90, 78], index=['Amit', 'Neha', 'Raj'], name='Marks')
print(s)
print("Series name:", s.name)
Output:
Amit 85
Neha 90
Raj 78
Name: Marks, dtype: int64
Series name: Marks
Using NumPy array as input:
# series_numpy.py
import pandas as pd
import numpy as np
arr = np.array([10, 20, 30, 40])
s = pd.Series(arr)
print(s)
"Write code to create a Series from a dictionary with subject names as index." β 2-mark question. Answer:
import pandas as pd
d = {'Math': 85, 'Science': 90, 'English': 78}
s = pd.Series(d)
print(s)1.4.3 Series Object Attributes π
Attributes are properties that tell you about the Series β like its size, type, values, etc.
Let's create a Series to demonstrate:
import pandas as pd
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
| Attribute | What It Returns | Example Output |
|---|---|---|
s.values |
All data as a NumPy array | [10 20 30 40] |
s.index |
All index labels | Index(['a','b','c','d']) |
s.dtype |
Data type of values | int64 |
s.size |
Total number of elements | 4 |
s.shape |
Dimensions as tuple | (4,) |
s.nbytes |
Memory used in bytes | 32 |
s.empty |
True if Series is empty | False |
s.name |
Name of the Series | None (if not set) |
s.ndim |
Number of dimensions | 1 |
# series_attributes.py
import pandas as pd
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print("Values:", s.values)
print("Index:", s.index)
print("Data type:", s.dtype)
print("Size:", s.size)
print("Shape:", s.shape)
print("Is empty?", s.empty)
Output:
Values: [10 20 30 40]
Index: Index(['a', 'b', 'c', 'd'], dtype='object')
Data type: int64
Size: 4
Shape: (4,)
Is empty? False
"What is the difference between s.size and s.shape?" β 1-mark question.
s.size β returns a number (total elements e.g. 4)
s.shape β returns a tuple showing dimensions e.g. (4,)
1.5 π Accessing a Series Object and its Elements
1.5.1 Accessing Individual Elements
Elements in a Series can be accessed using the index label (name) or position number.
# access_series.py
import pandas as pd
s = pd.Series([85, 90, 78, 92], index=['Amit', 'Neha', 'Raj', 'Priya'])
Using label (index name):
print(s['Neha']) # Output: 90
print(s['Raj']) # Output: 78
Using position number:
print(s[0]) # Output: 85 (first element)
print(s[2]) # Output: 78 (third element)
Accessing multiple elements (fancy indexing):
print(s[['Amit', 'Priya']]) # Pass a list of labels
Output:
Amit 85
Priya 92
dtype: int64
1.5.2 Extracting Slices from Series Object π°
Slicing means getting a portion (chunk) of the Series.
# slice_series.py
import pandas as pd
s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'])
Slicing by position (like Python lists):
print(s[1:4]) # Positions 1, 2, 3 (4 is EXCLUDED)
Output:
b 20
c 30
d 40
dtype: int64
Slicing by label:
print(s['b':'d']) # Labels b, c, d (d is INCLUDED!)
Output:
b 20
c 30
d 40
dtype: int64
s[1:4] using position β index 4 is EXCLUDED (like Python lists)
s['b':'d'] using label β 'd' is INCLUDED (label slicing is inclusive!)
Watch for this trick in output-based questions.
1.6 π οΈ Operations on Series Object
1.6.1 Modifying Elements of Series Object βοΈ
You can change values in a Series by assigning to the index:
# modify_series.py
import pandas as pd
s = pd.Series([85, 90, 78], index=['Amit', 'Neha', 'Raj'])
print("Before:", s['Neha'])
s['Neha'] = 95 # Modify using label
print("After:", s['Neha'])
s[2] = 82 # Modify using position
print("Raj's new marks:", s['Raj'])
Output:
Before: 90
After: 95
Raj's new marks: 82
Modifying multiple values:
s[['Amit', 'Raj']] = [88, 85] # Change multiple at once
print(s)
1.6.2 Renaming Indexes π·οΈ
You can rename the index labels of a Series using .rename() or by directly assigning a new index:
Method 1: Assign new index directly
# rename_index.py
import pandas as pd
s = pd.Series([85, 90, 78], index=['A', 'B', 'C'])
s.index = ['Amit', 'Neha', 'Raj'] # Replace all index labels
print(s)
Output:
Amit 85
Neha 90
Raj 78
dtype: int64
Method 2: Using rename() β selective renaming
s = s.rename({'Amit': 'AMIT', 'Neha': 'NEHA'}) # Rename specific labels
print(s)
Output:
AMIT 85
NEHA 90
Raj 78
dtype: int64
s.rename(...) returns a NEW Series. The original s is unchanged unless you write s = s.rename(...). This is true for most Pandas operations!
1.6.3 The head() and tail() Functions π
When a Series has many elements, you don't want to print ALL of them. head() and tail() let you peek at the first/last few:
# head_tail.py
import pandas as pd
s = pd.Series([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
print("First 3 elements:")
print(s.head(3))
print("\nLast 3 elements:")
print(s.tail(3))
print("\nDefault head (5):")
print(s.head()) # Default shows 5
Output:
First 3 elements:
0 10
1 20
2 30
dtype: int64
Last 3 elements:
7 80
8 90
9 100
dtype: int64
Default head (5):
0 10
1 20
2 30
3 40
4 50
dtype: int64
"What does s.head() return by default?" β 1-mark question.
Answer: By default (with no argument), head() returns the first 5 elements. tail() returns the last 5 elements.
1.6.4 Vector Operations on Series Objects β‘
Vectorization means performing an operation on every element at once β no loop needed!
# vector_operations.py
import pandas as pd
s = pd.Series([10, 20, 30, 40, 50])
print("Original:", s.values)
print("Add 5:", (s + 5).values)
print("Multiply by 2:", (s * 2).values)
print("Square root:", (s ** 0.5).values)
print("Divide by 10:", (s / 10).values)
Output:
Original: [10 20 30 40 50]
Add 5: [15 25 35 45 55]
Multiply by 2: [20 40 60 80 100]
Square root: [3.16 4.47 5.48 6.32 7.07]
Divide by 10: [1. 2. 3. 4. 5.]
Every operation applies to the entire Series automatically β no
forloop needed! This is the biggest advantage of Pandas over plain Python lists.
1.6.5 Arithmetic on Series Objects β
When you do arithmetic between two Series, Pandas aligns them by their index labels before calculating:
# arithmetic_series.py
import pandas as pd
marks_test1 = pd.Series({'Math': 45, 'Science': 40, 'English': 35})
marks_test2 = pd.Series({'Math': 50, 'Science': 45, 'English': 40})
total = marks_test1 + marks_test2
print("Total Marks:")
print(total)
percentage = (total / 2)
print("\nAverage:")
print(percentage)
Output:
Total Marks:
Math 95
Science 85
English 75
dtype: int64
Average:
Math 47.5
Science 42.5
English 37.5
dtype: float64
What happens when indexes don't match?
# mismatched_arithmetic.py
import pandas as pd
s1 = pd.Series({'a': 10, 'b': 20, 'c': 30})
s2 = pd.Series({'b': 5, 'c': 15, 'd': 25})
print(s1 + s2)
Output:
a NaN
b 25.0
c 45.0
d NaN
dtype: float64
When an index exists in one Series but NOT the other, the result is NaN (Not a Number) β representing a missing/unknown value!
"What is NaN in Pandas?" β 1-mark question. Answer: NaN (Not a Number) is Pandas' way of representing a missing or undefined value. It appears when arithmetic is performed between Series with non-matching index labels, or when data is missing in a dataset.
1.6.6 Filtering Entries in Series Objects π
Filtering means picking only the elements that match a condition:
# filter_series.py
import pandas as pd
marks = pd.Series([85, 45, 92, 38, 75, 60],
index=['Amit', 'Neha', 'Raj', 'Priya', 'Sita', 'Karan'])
# Who scored more than 70?
print("Students who passed with distinction (>70):")
print(marks[marks > 70])
print("\nStudents who failed (<50):")
print(marks[marks < 50])
print("\nStudents who scored between 60 and 90:")
print(marks[(marks >= 60) & (marks <= 90)])
Output:
Students who passed with distinction (>70):
Amit 85
Raj 92
Sita 75
dtype: int64
Students who failed (<50):
Neha 45
Priya 38
dtype: int64
Students who scored between 60 and 90:
Amit 85
Sita 75
Karan 60
dtype: int64
Greater than
marks[marks > 70]
Elements above 70Less than
marks[marks < 50]
Elements below 50AND condition
(marks > 60) & (marks < 90)
Between 60 and 90OR condition
(marks < 40)1.6.7 Sorting Series Values πΆ
Two ways to sort a Series:
# sort_series.py
import pandas as pd
s = pd.Series([30, 10, 50, 20, 40], index=['c', 'a', 'e', 'b', 'd'])
print("Sort by VALUES (ascending):")
print(s.sort_values())
print("\nSort by VALUES (descending):")
print(s.sort_values(ascending=False))
print("\nSort by INDEX (label):")
print(s.sort_index())
Output:
Sort by VALUES (ascending):
a 10
b 20
c 30
d 40
e 50
dtype: int64
Sort by VALUES (descending):
e 50
d 40
c 30
b 20
a 10
dtype: int64
Sort by INDEX (label):
a 10
b 20
c 30
d 40
e 50
dtype: int64
| Method | What it sorts by | Default |
|---|---|---|
sort_values() |
The data values | ascending=True |
sort_index() |
The index labels | ascending=True |
"Write code to sort a Series of prices in descending order." β 2-mark question.
Answer: s.sort_values(ascending=False)
1.7 π Series vs. 1D Data Structures
1.7.1 Series Objects vs. Lists
| Feature | Python List | Pandas Series |
|---|---|---|
| Index | Only 0, 1, 2... (numeric) | Custom labels (names, dates, etc.) |
| Data Type | Mixed types allowed | Uniform type (faster!) |
| Math Operations | Need loops | Vectorized β one line! |
| Missing Data | No concept | NaN support built-in |
| Size | Can grow/shrink | Fixed size after creation |
| Example | [85, 90, 78] |
pd.Series([85, 90, 78], index=['A','B','C']) |
# list_vs_series.py
import pandas as pd
# List: need loop to add 10 to all
my_list = [10, 20, 30]
result_list = [x + 10 for x in my_list] # Needs loop
# Series: no loop needed!
my_series = pd.Series([10, 20, 30])
result_series = my_series + 10 # No loop!
print("List result:", result_list)
print("Series result:", result_series.values)
1.7.2 Series Objects vs. Dictionaries
| Feature | Python Dictionary | Pandas Series |
|---|---|---|
| Index | Keys (any hashable type) | Index labels (same concept!) |
| Order | Maintained (Python 3.7+) | Always maintains order |
| Math Operations | Not supported | Vectorized math built-in |
| Slicing | Limited (no range slicing) | Full slicing support |
| Alignment | No | β Auto-aligns by index during arithmetic |
| Convert to Series | pd.Series(dict) |
Already a Series |
# dict_vs_series.py
import pandas as pd
# Dictionary
d = {'Math': 85, 'Science': 90}
# d + 5 β This would cause ERROR!
# Series (from dictionary)
s = pd.Series(d)
print(s + 5) # β
Works perfectly!
Output:
Math 90
Science 95
dtype: int64
1.7.3 Difference between NumPy Arrays and Series Objects
| Feature | NumPy Array (1D) | Pandas Series |
|---|---|---|
| Index | Only 0, 1, 2... (numeric) | Custom index labels |
| Data Type | Must be uniform | Must be uniform |
| NaN Handling | Limited | Full NaN support |
| Mathematical Ops | β Fast | β Fast (uses NumPy inside) |
| Label-based Access | β Not available | β
s['label'] works |
| Index Alignment | β No | β Auto-aligns by label |
# numpy_vs_series.py
import pandas as pd
import numpy as np
arr = np.array([10, 20, 30])
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
# NumPy array: only position access
print("NumPy arr[0]:", arr[0]) # Output: 10
# Series: both position AND label access
print("Series s['b']:", s['b']) # Output: 20
print("Series s[1]:", s[1]) # Output: 20
"Differentiate between a Pandas Series and a NumPy 1D Array." β 2-mark question asked often! Key differences: Series has custom labels (NumPy only has numeric positions). Series supports index alignment in arithmetic. Series has better NaN handling.
1.8 π DataFrame Data Structure
A DataFrame is a 2-dimensional table with:
- Rows β each row is one record
- Columns β each column is one attribute
- Row Index β labels for rows
- Column Labels β headers for columns
Name Math Science English
0 Amit 85 88 75
1 Neha 90 92 88
2 Raj 78 80 72
3 Priya 92 95 89
β β
Row Index Column Labels
Think of a DataFrame as a collection of Series β each column is a Series, all sharing the same row index!
1.9 π Creating and Displaying a DataFrame
Method 1: Dictionary of Lists (Most Common! β)
Keys = Column names, Values = Column data as lists
# df_from_dict.py
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj', 'Priya'],
'Math': [85, 90, 78, 92],
'Science': [88, 92, 80, 95],
'English': [75, 88, 72, 89]
}
df = pd.DataFrame(data)
print(df)
Output:
Name Math Science English
0 Amit 85 88 75
1 Neha 90 92 88
2 Raj 78 80 72
3 Priya 92 95 89
With custom row index:
df = pd.DataFrame(data, index=['S1', 'S2', 'S3', 'S4'])
print(df)
Output:
Name Math Science English
S1 Amit 85 88 75
S2 Neha 90 92 88
S3 Raj 78 80 72
S4 Priya 92 95 89
Method 2: List of Dictionaries
Each dictionary = one row. Keys = column names.
# df_from_list_of_dicts.py
import pandas as pd
data = [
{'Name': 'Amit', 'Marks': 85, 'Grade': 'A'},
{'Name': 'Neha', 'Marks': 90, 'Grade': 'A+'},
{'Name': 'Raj', 'Marks': 78, 'Grade': 'B+'},
]
df = pd.DataFrame(data)
print(df)
Output:
Name Marks Grade
0 Amit 85 A
1 Neha 90 A+
2 Raj 78 B+
Method 3: Dictionary of Series
# df_from_series.py
import pandas as pd
marks = pd.Series([85, 90, 78], index=['Amit', 'Neha', 'Raj'])
grades = pd.Series(['A', 'A+', 'B+'], index=['Amit', 'Neha', 'Raj'])
df = pd.DataFrame({'Marks': marks, 'Grade': grades})
print(df)
Output:
Marks Grade
Amit 85 A
Neha 90 A+
Raj 78 B+
Method 4: 2D NumPy Array
# df_from_numpy.py
import pandas as pd
import numpy as np
arr = np.array([[85, 88, 75],
[90, 92, 88],
[78, 80, 72]])
df = pd.DataFrame(arr,
columns=['Math', 'Science', 'English'],
index=['Amit', 'Neha', 'Raj'])
print(df)
Output:
Math Science English
Amit 85 88 75
Neha 90 92 88
Raj 78 80 72
"Write code to create a DataFrame with student name, marks in 3 subjects." β 3-mark question, most common! Use Method 1 (Dictionary of Lists) β it's the cleanest and most widely tested.
1.10 π’ DataFrame Attributes
Using this DataFrame for all examples:
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj', 'Priya'],
'Marks': [85, 90, 78, 92],
'Grade': ['A', 'A+', 'B+', 'A+']
}
df = pd.DataFrame(data, index=['S1', 'S2', 'S3', 'S4'])
| Attribute | What It Returns | Output |
|---|---|---|
df.shape |
(rows, columns) as tuple | (4, 3) |
df.index |
Row labels | Index(['S1','S2','S3','S4']) |
df.columns |
Column names | Index(['Name','Marks','Grade']) |
df.dtypes |
Data type of each column | (table of types) |
df.values |
All data as 2D NumPy array | array([[...]]) |
df.size |
Total cells (rows Γ cols) | 12 |
df.ndim |
Number of dimensions | 2 |
df.empty |
True if DataFrame is empty | False |
df.T |
Transpose (flip rowsβcols) | (flipped table) |
df.head(n) |
First n rows (default 5) | First 4 rows |
df.tail(n) |
Last n rows (default 5) | Last rows |
df.info() |
Summary of types and nulls | Technical summary |
df.describe() |
Statistics (mean, min, max) | Stats table |
# df_attributes_demo.py
print("Shape:", df.shape)
print("Columns:", df.columns.tolist())
print("Index:", df.index.tolist())
print("Size:", df.size)
print("Dimensions:", df.ndim)
print("\nFirst 2 rows:")
print(df.head(2))
Output:
Shape: (4, 3)
Columns: ['Name', 'Marks', 'Grade']
Index: ['S1', 'S2', 'S3', 'S4']
Size: 12
Dimensions: 2
First 2 rows:
Name Marks Grade
S1 Amit 85 A
S2 Neha 90 A+
1.11 π DataFrame vs. Series and 2D NumPy Array
1.11.1 DataFrame vs. Series
| Feature | Series | DataFrame |
|---|---|---|
| Dimensions | 1D (one column) | 2D (rows + columns) |
| Index | One index (row labels) | Row index + Column labels |
| Data | One column of data | Multiple columns |
| Access | s['label'] |
df['column']['row'] |
| Relationship | A single column | A collection of Series |
| When to use | One variable (marks only) | Multiple variables (name, marks, grade) |
# series_vs_df.py
import pandas as pd
# Series β one column
s = pd.Series([85, 90, 78], name='Marks', index=['Amit', 'Neha', 'Raj'])
# DataFrame β multiple columns (collection of Series!)
df = pd.DataFrame({
'Marks': [85, 90, 78],
'Grade': ['A', 'A+', 'B+']
}, index=['Amit', 'Neha', 'Raj'])
print("Series:\n", s)
print("\nDataFrame:\n", df)
1.11.2 DataFrame vs. 2D NumPy Array (ndarray)
| Feature | 2D NumPy Array | Pandas DataFrame |
|---|---|---|
| Row Labels | Only 0, 1, 2... | Custom labels |
| Column Labels | Only 0, 1, 2... | Named columns |
| Mixed Types | β All must be same | β Each column can differ |
| NaN Support | Limited | β Full support |
| Label Access | β Position only | β Name + position |
| Pretty Printing | Raw array format | Table format |
"How is a DataFrame different from a 2D NumPy Array?" β 2-mark question. Key: DataFrame has named rows and columns (not just numbers), supports mixed data types per column, and has built-in NaN handling. NumPy arrays are faster but less flexible.
1.12 π― Selecting or Accessing Data
Selecting and accessing data is used throughout the rest of the chapter, so learn this section thoroughly.
Our reference DataFrame for all examples:
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj', 'Priya', 'Sita'],
'Marks': [85, 90, 78, 92, 65],
'Grade': ['A', 'A+', 'B+', 'A+', 'B'],
'City': ['Delhi', 'Mumbai', 'Delhi', 'Pune', 'Delhi']
}
df = pd.DataFrame(data, index=['R1', 'R2', 'R3', 'R4', 'R5'])
print(df)
Name Marks Grade City
R1 Amit 85 A Delhi
R2 Neha 90 A+ Mumbai
R3 Raj 78 B+ Delhi
R4 Priya 92 A+ Pune
R5 Sita 65 B Delhi
1.12.1 Selecting / Accessing a Column
# select_one_column.py
print(df['Name']) # Returns a Series
print(type(df['Name'])) # <class 'pandas.core.series.Series'>
Output:
R1 Amit
R2 Neha
R3 Raj
R4 Priya
R5 Sita
Name: Name, dtype: object
1.12.2 Selecting / Accessing Multiple Columns
Use double brackets [[ ]] to select multiple columns β you're passing a list of column names:
# select_multiple_columns.py
print(df[['Name', 'Marks']]) # Double brackets!
print(type(df[['Name', 'Marks']])) # DataFrame (not Series!)
Output:
Name Marks
R1 Amit 85
R2 Neha 90
R3 Raj 78
R4 Priya 92
R5 Sita 65
df['Name'] β returns a Series (one column)
df[['Name']] β returns a DataFrame (still one column but in table form)
df[['Name', 'Marks']] β returns a DataFrame with two columns
This is one of the top mistakes in board exams!
1.12.3 Selecting a Subset using Row/Column Names β loc[] π―
loc = Label-based access. Use actual names to select.
Syntax: df.loc[row_label, column_label]
# loc_demo.py
# Access ONE cell: row R2, column Marks
print(df.loc['R2', 'Marks']) # Output: 90
# Access ONE full row:
print(df.loc['R3'])
# Access specific rows AND columns:
print(df.loc[['R1', 'R3'], ['Name', 'Marks']])
# Access a range of rows (INCLUSIVE on both ends!):
print(df.loc['R1':'R3'])
# Access all rows, specific columns:
print(df.loc[:, ['Name', 'City']])
Output of df.loc[['R1', 'R3'], ['Name', 'Marks']]:
Name Marks
R1 Amit 85
R3 Raj 78
1.12.4 Selecting Rows/Columns β iloc[] π’
iloc = Integer-position-based access. Use numbers (0, 1, 2...) to select.
Syntax: df.iloc[row_number, column_number]
# iloc_demo.py
# Access ONE cell: 1st row (index 1), 2nd column (index 1)
print(df.iloc[1, 1]) # Output: 90 (Neha's Marks)
# Access ONE full row:
print(df.iloc[2]) # 3rd row (Raj)
# Access a range of rows (EXCLUSIVE end!):
print(df.iloc[0:3]) # Rows 0, 1, 2 (NOT 3!)
# Access specific rows and columns by number:
print(df.iloc[[0, 2], [0, 1]]) # Rows 0,2 and Columns 0,1
Output of df.iloc[0:3]:
Name Marks Grade City
R1 Amit 85 A Delhi
R2 Neha 90 A+ Mumbai
R3 Raj 78 B+ Delhi
loc vs iloc β The Critical Comparison:
| Feature | loc |
iloc |
|---|---|---|
| Based on | Labels (names) | Integer positions (numbers) |
| Row access | df.loc['R2'] |
df.iloc[1] |
| Column access | df.loc[:, 'Name'] |
df.iloc[:, 0] |
| Slice end | INCLUSIVE (includes last) | EXCLUSIVE (excludes last) |
| Remember as | loc = Label | iloc = Integer |
graph LR
LOC["loc\nUse LABELS\n'R1' to 'R3'\nInclusive end"]
ILOC["iloc\nUse NUMBERS\n0 to 2\nExclusive end"]
style LOC fill:#4CAF50,color:#fff
style ILOC fill:#2196F3,color:#fff
"What is the difference between loc and iloc?" β 2-mark question.
Answer: loc uses row/column labels (names) and the end of a slice is inclusive. iloc uses integer positions (0,1,2...) and the end of a slice is exclusive (like Python list slicing).
1.12.5 Selecting / Accessing Individual Value
Two ways to access one specific cell:
# access_individual_value.py
# Method 1: Using loc (label-based)
print(df.loc['R2', 'Marks']) # Output: 90
# Method 2: Using iloc (position-based)
print(df.iloc[1, 1]) # Output: 90
# Method 3: Using at (fastest for single value, label-based)
print(df.at['R2', 'Marks']) # Output: 90
# Method 4: Using iat (fastest for single value, position-based)
print(df.iat[1, 1]) # Output: 90
atandiatare slightly faster thanlocandilocfor accessing a SINGLE value, butlocandilocare more versatile for ranges.
1.12.6 Selecting DataFrame Rows/Columns Based on Boolean Conditions π
Filter rows based on a condition β this is the most practical use case!
# boolean_filter.py
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj', 'Priya', 'Sita'],
'Marks': [85, 90, 78, 92, 65],
'City': ['Delhi', 'Mumbai', 'Delhi', 'Pune', 'Delhi']
}
df = pd.DataFrame(data, index=['R1', 'R2', 'R3', 'R4', 'R5'])
# Students with Marks > 80:
print("Marks above 80:")
print(df[df['Marks'] > 80])
# Students from Delhi:
print("\nStudents from Delhi:")
print(df[df['City'] == 'Delhi'])
# Students from Delhi with Marks > 75:
print("\nDelhi students with Marks > 75:")
print(df[(df['City'] == 'Delhi') & (df['Marks'] > 75)])
Output (Marks > 80):
Name Marks City
R1 Amit 85 Delhi
R2 Neha 90 Mumbai
R4 Priya 92 Pune
"Write code to display all students who scored more than 80 marks." β 2-mark question!
Answer: print(df[df['Marks'] > 80])
1.13 β Adding/Modifying Rows/Columns in DataFrames
1.13.1 Adding/Modifying a Column
Adding a new column:
# add_column.py
import pandas as pd
df = pd.DataFrame({
'Name': ['Amit', 'Neha', 'Raj'],
'Marks': [85, 90, 78]
})
# Add new column with calculated values:
df['Percentage'] = df['Marks'] / 100 * 100
df['Grade'] = ['A', 'A+', 'B+']
df['Status'] = 'Pass' # Same value for all rows
print(df)
Output:
Name Marks Percentage Grade Status
0 Amit 85 85.0 A Pass
1 Neha 90 90.0 A+ Pass
2 Raj 78 78.0 B+ Pass
Modifying an existing column:
# Increase all marks by 5 (bonus marks):
df['Marks'] = df['Marks'] + 5
print(df['Marks'])
Output:
0 90
1 95
2 83
Name: Marks, dtype: int64
1.13.2 Adding/Modifying a Row
Adding a new row using loc[]:
# add_row.py
import pandas as pd
df = pd.DataFrame({
'Name': ['Amit', 'Neha'],
'Marks': [85, 90]
}, index=['R1', 'R2'])
# Add a new row:
df.loc['R3'] = ['Raj', 78]
print(df)
Output:
Name Marks
R1 Amit 85
R2 Neha 90
R3 Raj 78
Modifying an existing row:
df.loc['R2'] = ['NEHA', 95] # Replace entire row R2
1.13.3 Modifying a Single Cell
# modify_cell.py
import pandas as pd
df = pd.DataFrame({
'Name': ['Amit', 'Neha', 'Raj'],
'Marks': [85, 90, 78]
}, index=['R1', 'R2', 'R3'])
# Using loc:
df.loc['R2', 'Marks'] = 95
print("After update:", df.loc['R2', 'Marks']) # Output: 95
# Using iloc:
df.iloc[0, 1] = 88
print("After update:", df.iloc[0, 1]) # Output: 88
# Using at:
df.at['R3', 'Marks'] = 82
print("After update:", df.at['R3', 'Marks']) # Output: 82
1.14 β Deleting/Renaming Columns/Rows
1.14.1 Deleting Rows/Columns in a DataFrame
Using the drop() method:
# delete_demo.py
import pandas as pd
df = pd.DataFrame({
'Name': ['Amit', 'Neha', 'Raj', 'Priya'],
'Marks': [85, 90, 78, 92],
'Grade': ['A', 'A+', 'B+', 'A+']
}, index=['R1', 'R2', 'R3', 'R4'])
Delete a COLUMN (axis=1):
df2 = df.drop('Grade', axis=1) # Returns new df WITHOUT Grade column
print(df2)
Output:
Name Marks
R1 Amit 85
R2 Neha 90
R3 Raj 78
R4 Priya 92
Delete a ROW (axis=0):
df3 = df.drop('R3', axis=0) # Removes Raj's row
print(df3)
Output:
Name Marks Grade
R1 Amit 85 A
R2 Neha 90 A+
R4 Priya 92 A+
Delete multiple rows or columns:
df.drop(['R1', 'R4'], axis=0) # Delete multiple rows
df.drop(['Grade', 'Marks'], axis=1) # Delete multiple columns
Using inplace=True to modify the original:
df.drop('Grade', axis=1, inplace=True) # Changes df itself, no need to reassign
df.drop('Grade', axis=1) returns a NEW DataFrame β df itself is unchanged!
Use inplace=True OR reassign: df = df.drop('Grade', axis=1) to actually change df.
"Write code to delete the column 'Grade' from a DataFrame df." β 2-mark question!
Answer: df = df.drop('Grade', axis=1) or df.drop('Grade', axis=1, inplace=True)
Remember: axis=1 for columns, axis=0 for rows!
1.14.2 Renaming Rows/Columns
Using the rename() method:
# rename_demo.py
import pandas as pd
df = pd.DataFrame({
'Name': ['Amit', 'Neha'],
'Marks': [85, 90]
}, index=['R1', 'R2'])
# Rename COLUMNS:
df2 = df.rename(columns={'Name': 'StudentName', 'Marks': 'Score'})
print(df2)
Output:
StudentName Score
R1 Amit 85
R2 Neha 90
Rename ROWS (index):
df3 = df.rename(index={'R1': 'Row1', 'R2': 'Row2'})
print(df3)
Output:
Name Marks
Row1 Amit 85
Row2 Neha 90
Rename both at once:
df4 = df.rename(columns={'Name': 'StudentName'},
index={'R1': 'Row1'})
1.15 π΅ More on DataFrame Indexing β BOOLEAN INDEXING
Boolean indexing is about creating and using True/False arrays to filter data.
1.15.1 Creating DataFrames with Boolean Indexes
A Boolean index is a Series of True/False values used as row labels:
# boolean_index.py
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj', 'Priya'],
'Marks': [85, 90, 78, 92],
}
df = pd.DataFrame(data)
# Creating a Boolean Series:
bool_index = pd.Series([True, False, True, False])
print("Boolean Series:")
print(bool_index)
print("\nDataFrame with Boolean Index:")
print(df[bool_index])
Output:
Boolean Series:
0 True
1 False
2 True
3 False
dtype: bool
DataFrame with Boolean Index:
Name Marks
0 Amit 85
2 Raj 78
When you pass a Boolean Series to a DataFrame, it keeps only the rows where the value is True and skips the rows where it's False!
1.15.2 Accessing Rows from DataFrames with Boolean Indexes π
The real power β creating boolean conditions dynamically from data:
# boolean_access.py
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj', 'Priya', 'Sita'],
'Marks': [85, 90, 78, 92, 65],
'Subject': ['Math', 'Science', 'Math', 'Science', 'Math'],
'City': ['Delhi', 'Mumbai', 'Delhi', 'Pune', 'Delhi']
}
df = pd.DataFrame(data)
# Step 1: Create a boolean condition (this creates True/False series)
condition = df['Marks'] > 80
print("Boolean condition:")
print(condition)
print()
# Step 2: Use it to filter the DataFrame
print("Students with Marks > 80:")
print(df[condition])
Output:
Boolean condition:
0 True
1 True
2 False
3 True
4 False
dtype: bool
Students with Marks > 80:
Name Marks Subject City
0 Amit 85 Math Delhi
1 Neha 90 Science Mumbai
3 Priya 92 Science Pune
Combining Multiple Conditions:
# multiple_conditions.py
# AND condition (&): Both must be True
print("Math students scoring > 80:")
print(df[(df['Subject'] == 'Math') & (df['Marks'] > 80)])
# OR condition (|): At least one must be True
print("\nDelhi students OR students scoring > 88:")
print(df[(df['City'] == 'Delhi') | (df['Marks'] > 88)])
# NOT condition (~): Reverse True/False
print("\nStudents NOT from Delhi:")
print(df[~(df['City'] == 'Delhi')])
Output of Math AND Marks > 80:
Name Marks Subject City
0 Amit 85 Math Delhi
AND
Both conditions must be True
df[(df['Marks'] > 80) & (df['City'] == 'Delhi')]OR
At least one condition TrueNOT
Reverses True/False
df[~(df['City'] == 'Mumbai')]"Write a query to display students who scored more than 75 AND are from Delhi." β 3-mark question!
Answer: df[(df['Marks'] > 75) & (df['City'] == 'Delhi')]
Remember: Use & (not and) and | (not or) inside Pandas conditions!
β οΈ Common Errors and Misconceptions
| Mistake | What Goes Wrong | Correct Code |
|---|---|---|
β df['Name', 'Marks'] |
TypeError | β
df[['Name', 'Marks']] (double brackets!) |
β df.loc[0:2] thinking 2 is excluded |
Includes index label 2! | β
Use df.iloc[0:2] if you want 2 excluded |
β df.drop('Name') β column not deleted |
axis not specified | β
df.drop('Name', axis=1) |
β df.drop('R1') doesn't change original |
drop() returns new df | β
df = df.drop('R1', axis=0) or inplace=True |
β df[(df['M'] > 80) and (df['C'] == 'Delhi')] |
Use of Python and |
β
Use & not and |
β s[1:3] for label slice includes 3 |
Position slice excludes 3 | β
Label slice s['b':'d'] INCLUDES 'd' |
β Forgetting import pandas as pd |
NameError: pd not defined | β Always import at the top! |
π Quick Revision
Series β One-Line Summary:
- Create:
pd.Series(list/dict/scalar, index=[...]) - Access:
s['label']ors[position] - Slice:
s['a':'c'](inclusive) ors[0:3](exclusive) - Sort:
sort_values()/sort_index() - Filter:
s[s > 50]
DataFrame β One-Line Summary:
- Create:
pd.DataFrame(dict_of_lists, index=[...]) - Column:
df['col']β Series;df[['col1','col2']]β DataFrame - Row by label:
df.loc['R1'] - Row by number:
df.iloc[0] - Filter:
df[df['col'] > value] - Add column:
df['NewCol'] = values - Delete:
df.drop('col', axis=1)ordf.drop('row', axis=0) - Rename:
df.rename(columns={'old':'new'})
loc vs iloc quick rule:
loc= Label β Inclusive sliceiloc= Integer β Exclusive slice
π― Sample Board Exam Questions
Q1: Very Short Answer [1 mark each]
a) Which Pandas data structure is 2-dimensional? β DataFrame
b) Write the command to import Pandas with alias pd.
β import pandas as pd
c) What does df.shape return for a DataFrame with 5 rows and 3 columns?
β (5, 3) β a tuple (rows, columns)
d) What does head() return by default?
β First 5 rows
e) Which attribute gives data type of all columns in a DataFrame?
β df.dtypes
Q2: Short Answer [2 marks]
Q: Differentiate between loc and iloc with example.
loc uses label names to access rows/columns and the slice end is inclusive.
iloc uses integer positions (0, 1, 2...) and the slice end is exclusive.
df.loc['R1':'R3'] # Returns R1, R2, R3 (R3 INCLUDED)
df.iloc[0:3] # Returns rows 0, 1, 2 (3 EXCLUDED)
Q3: Program Writing [3 marks]
Q: Create a DataFrame with columns Name, Age, City for 3 students. Display students from 'Delhi'.
import pandas as pd
data = {
'Name': ['Amit', 'Neha', 'Raj'],
'Age': [16, 17, 16],
'City': ['Delhi', 'Mumbai', 'Delhi']
}
df = pd.DataFrame(data)
print("Delhi students:")
print(df[df['City'] == 'Delhi'])
Q4: Program Writing [3 marks]
Q: Create a Series of 5 subjects and their marks. Display subjects with marks above 80. Sort in descending order.
import pandas as pd
marks = pd.Series(
[95, 72, 88, 65, 91],
index=['Math', 'English', 'Science', 'Hindi', 'IP']
)
print("Subjects above 80:")
print(marks[marks > 80])
print("\nSorted in descending order:")
print(marks.sort_values(ascending=False))
Q5: Output Based [2 marks]
Q: What is the output?
import pandas as pd
s = pd.Series([10, 20, 30, 40, 50], index=['a','b','c','d','e'])
print(s['b':'d'])
print(s[1:3])
Output:
b 20
c 30
d 40
dtype: int64
b 20
c 30
dtype: int64
(Note: Label slice 'b':'d' INCLUDES 'd'. Position slice 1:3 EXCLUDES index 3)
βοΈ Practice Problems
-
Create a Series of 6 students' marks and display only those who scored between 70 and 90.
-
Create a DataFrame for a product catalogue with columns: ProductName, Price, Quantity, Category. Display all products under βΉ500.
-
Write code to add a 'Total' column to a student DataFrame that has 'Test1' and 'Test2' columns.
-
Create a DataFrame and delete a specific row and column. Show the output before and after deletion.
-
Using
loc, select rows R2 to R4 and columns 'Name' and 'Marks' from a DataFrame. -
What is the difference between
sort_values()andsort_index()in a Series? Give examples. -
Write code to rename the column 'Marks' to 'Score' and the index label 'R1' to 'Row1'.
-
Create a DataFrame with student data. Using boolean indexing, display students who are from 'Mumbai' OR scored above 85.
-
Explain with code what happens when you add two Series with different index labels.
-
Write code to access the element at the 3rd row and 2nd column of a DataFrame using both
locandiloc.