Aimify IP Material
Class 12 · Chapter 2

Data Handling using Pandas - I

In CBSE syllabusUnit 1: Data Handling using Pandas and Data Visualization (25 marks)~24 periods
25 MCQs40 written Qs10 practicals

Overview

Pandas is the Python library for working with labelled, table-like data. This chapter builds the two Pandas data structures, the one-dimensional Series and the two-dimensional DataFrame. Students learn to create them, select, filter, change and delete data in them, and move data between DataFrames and CSV files.

The whole chapter is in the CBSE 2025-26 syllabus: Python libraries, Series (creation, mathematical operations, head()/tail(), selection, indexing, slicing), DataFrames (creation, display, iteration, add/select/delete/rename rows and columns, label and Boolean indexing) and import/export between CSV files and DataFrames. Joining DataFrames with pd.concat() is marked Enrichment.

Learning outcomes

  • Students will be able to explain why Pandas is used alongside NumPy and Matplotlib, and import it with the alias pd.
  • Students will be able to create a Series from a list, an ndarray, a dictionary and a scalar value, with or without a custom index.
  • Students will be able to use Series attributes and methods (index, values, dtype, shape, size, name, empty, head(), tail(), count()).
  • Students will be able to perform mathematical operations on Series and predict where index alignment produces NaN.
  • Students will be able to select, index and slice Series and DataFrames using labels (loc), positions (iloc) and Boolean conditions.
  • Students will be able to create DataFrames from a dictionary of lists, a dictionary of Series, a list of dictionaries and a CSV file.
  • Students will be able to add, select, delete and rename rows and columns, and iterate over a DataFrame with iterrows() and items().
  • Students will be able to import and export data with read_csv() and to_csv() using sep, header, index, names and index_col.

Lesson plan

Period 1
Why Pandas?

Show the class a marks register on the board and ask how they would find the topper with a Python list. Introduce NumPy, Pandas and Matplotlib and what each does. In the lab, students run pip install pandas (or check it with import pandas as pd; print(pd.__version__)) and note the version their computer has.

Periods 2-3
Creating a Series

Draw a Series as two columns on the board: index and values. Students create Series from a list, a scalar, an ndarray and a dictionary of state capitals. Ask them to break it on purpose: give an index of the wrong length and read the ValueError.

Period 4
Series attributes

Give each lab pair one Series of cricket scores. They print every attribute (index, values, dtype, shape, size, name, empty) and fill a worksheet table of attribute, output and meaning.

Periods 5-6
Indexing and slicing a Series

Board drill: write a five-element Series with labels 'a' to 'e' and ask students to call out the output of s[1:3], s['b':'d'], s.iloc[-1] and s.loc[['a','e']]. Stress that label slices include the end. Lab: verify every answer and record mismatches.

Period 7
Boolean filtering, head() and tail()

Students filter a Series of daily temperatures for hot days (> 40), then combine conditions with & and |. Short quiz on head() and tail() defaults.

Periods 8-9
Mathematical operations and alignment

Use two shops' sales Series with partly matching item labels. Students predict s1 + s2 on paper first (marking NaN), then run it and fix the gaps with add(..., fill_value=0).

Periods 10-11
Creating DataFrames

Build the same class result table four ways: dictionary of lists, list of dictionaries, dictionary of Series and a CSV file. Discuss which keys become column labels and where NaN appears.

Periods 12-13
Attributes, display, head() and tail()

Lab worksheet: students print index, columns, dtypes, values, shape, size, T and empty for an exam-result DataFrame (CBSE practical 4) and write one line on each.

Periods 14-16
Selecting with loc, iloc and slices

Project a train timetable DataFrame. Ask students to write the expression for given questions ("departure of 12951", "first three trains, two columns"). Compare inclusive loc slices and exclusive iloc slices.

Period 17
Boolean indexing and duplicates

Students filter the result DataFrame for distinctions, for one section, and for rows that repeat (duplicated(), CBSE practical 5). Remind them of brackets around each condition.

Periods 18-19
Add, delete and rename

Students add a Total column and a new student row, drop a column with drop(..., axis=1) and with del, and rename columns. Discuss why df = df.drop(...) is needed and what the older inplace=True did.

Period 20
Iteration

Students print a one-line report card per student with iterrows(), then column-wise averages with items(). Mention that iteritems() no longer exists in newer Pandas.

Periods 21-22
CSV import and export

Students open marks.csv in a text editor, then read it with read_csv() using index_col, header=None and names. They export a filtered DataFrame with to_csv(sep=';', index=False) and open the new file to check it (CBSE practical 6).

Periods 23-24
Revision and output-prediction test

Ten-minute output-prediction quiz from this chapter's short questions, then peer marking. Assign the practicals for the practical file.

Notes

Python libraries for data: NumPy, Pandas and Matplotlib

A library is a collection of ready-made modules that we import instead of writing the code ourselves. Three libraries are used together for data work:

  • NumPy (Numerical Python) stores numbers in fast, fixed-type arrays called ndarray.
  • Pandas (from panel data) adds labelled data structures, the Series and the DataFrame, plus easy reading and writing of files.
  • Matplotlib draws charts such as line plots, bar graphs and histograms.

Why Pandas when NumPy exists? A NumPy array holds one data type, while a DataFrame can hold names, marks and dates in different columns. Pandas also gives each row and column a label, so we can say df['Maths'] instead of remembering that Maths is column 2. Pandas is built on NumPy, so the two work together.

Install Pandas from the command prompt with pip install pandas (Python must already be installed). By convention it is imported with the alias pd.

Python
import pandas as pd
import numpy as np
arr = np.array([72, 85, 64])
print(arr)
print(pd.Series(arr, index=['Ravi', 'Sana', 'Tara']))
print(pd.DataFrame({'Marks': arr, 'House': ['Red', 'Blue', 'Red']}))
Output
[72 85 64]
Ravi    72
Sana    85
Tara    64
dtype: int64
   Marks House
0     72   Red
1     85  Blue
2     64   Red
Teacher tip

Ask students to print pd.__version__ once in the lab. Schools run anything from Pandas 1.x to 3.x, and a few behaviours in this chapter (noted below) depend on the version.

Data structures in Pandas: Series and DataFrame

A data structure is a way of storing data together with the operations allowed on it. Pandas has two that we study:

  • Series: a one-dimensional array of values with an index (row labels). Think of a single column of a marks register.
  • DataFrame: a two-dimensional table with a row index and column labels. Each column of a DataFrame is a Series, and different columns may have different data types.

The NCERT text also mentions a third structure, the Panel, which has been removed from Pandas and is not used any more.

FeatureSeriesDataFrameNumPy ndarray
Dimensions1 (one column)2 (rows and columns)1 or more
LabelsRow indexRow index and column labelsOnly integer positions
Data typesOne type for all valuesEach column can have its own typeOne type for all values
SizeValues can change; size can growValues and size can changeFixed size once created
Missing dataShown as NaNShown as NaNNo built-in notion
Arithmetic on two objectsMatched by index labelMatched by row and column labelsMatched by position; shapes must agree

Creating a Series from a list or a scalar value

The general form is pd.Series(data, index=..., dtype=..., name=...). Only data is needed.

  • With a list, Pandas gives a default index 0, 1, 2, ...
  • Pass index=[...] to use your own labels. The number of labels must equal the number of values, or Pandas raises a ValueError.
  • With a scalar (a single value), you must give an index; the value is repeated once for every label.

The printout shows the index on the left, values on the right and the dtype at the bottom.

Python
import pandas as pd
runs = pd.Series([45, 102, 7, 63])
print(runs)
print()
runs = pd.Series([45, 102, 7, 63], index=['Rohit', 'Virat', 'Shubman', 'Rishabh'])
print(runs)
print()
fee = pd.Series(1500, index=['Jan', 'Feb', 'Mar'])
print(fee)
Output
0     45
1    102
2      7
3     63
dtype: int64

Rohit       45
Virat      102
Shubman      7
Rishabh     63
dtype: int64

Jan    1500
Feb    1500
Mar    1500
dtype: int64
Teacher tip

A favourite board question: "Create a Series of 5 elements, all equal to 10, with index 'a' to 'e'." Answer: pd.Series(10, index=['a','b','c','d','e']). Without index, a scalar gives a Series of just one element.

Creating a Series from a NumPy ndarray

A one-dimensional ndarray can be passed straight to pd.Series(). The values and their order stay the same; Pandas adds an index. A custom index must again match the length of the array.

The dtype of the Series is taken from the array: an array of whole numbers gives an integer Series, and mixing in one decimal makes all values float64.

Python
import pandas as pd
import numpy as np
temps = np.array([38.5, 41.2, 43.0, 39.8])
s = pd.Series(temps, index=['Jaipur', 'Nagpur', 'Banda', 'Delhi'])
print(s)
print()
try:
    pd.Series(np.array([1, 2, 3]), index=['x', 'y'])
except ValueError as e:
    print('ValueError:', e)
Output
Jaipur    38.5
Nagpur    41.2
Banda     43.0
Delhi     39.8
dtype: float64

ValueError: Length of values (3) does not match length of index (2)
Teacher tip

On Windows computers with NumPy older than 2.0, np.array([1, 2, 3]) is int32, so a Series made from it prints dtype: int32. A Series made from a Python list is int64 everywhere. Accept either in class.

Creating a Series from a dictionary

When the data is a dictionary, the keys become the index and the values become the data.

If you also pass index=, Pandas picks the values for those labels in the order you list them. A label that is not a key gets NaN (Not a Number), and because NaN is a float, the whole Series becomes float64.

Python
import pandas as pd
capitals = {'Kerala': 'Thiruvananthapuram', 'Punjab': 'Chandigarh', 'Assam': 'Dispur'}
s = pd.Series(capitals)
print(s)
print()
medals = {'India': 6, 'Kenya': 11, 'Japan': 45}
m = pd.Series(medals, index=['Japan', 'India', 'Nepal'])
print(m)
Output
Kerala    Thiruvananthapuram
Punjab            Chandigarh
Assam                 Dispur
dtype: object

Japan    45.0
India     6.0
Nepal     NaN
dtype: float64
Teacher tip

Text values show dtype: object in Pandas 1.x and 2.x. Pandas 3 prints dtype: str for the same Series. The outputs on these pages use the object form that school computers show.

Attributes of a Series

An attribute is a property read with a dot and no brackets (methods such as head() have brackets). The name of a Series and the name of its index can be set, and they appear in the printout.

AttributeGivesExample result
s.indexThe row labelsIndex(['Mon', 'Tue', ...], dtype='object')
s.valuesThe data as a NumPy array[41 39 44 40]
s.dtypeData type of the valuesint64
s.shapeTuple with the number of elements(4,)
s.sizeNumber of elements (NaN included)4
s.ndimNumber of dimensions (always 1)1
s.nameName of the Series'MaxTemp'
s.index.nameName of the index'Day'
s.emptyTrue if there are no elementsFalse
s.hasnansTrue if any value is NaNFalse
Python
import pandas as pd
s = pd.Series([41, 39, 44, 40], index=['Mon', 'Tue', 'Wed', 'Thu'])
s.name = 'MaxTemp'
s.index.name = 'Day'
print(s)
print(s.index)
print(s.values)
print(s.dtype, s.shape, s.size, s.ndim)
print(s.empty, pd.Series(dtype='float64').empty)
Output
Day
Mon    41
Tue    39
Wed    44
Thu    40
Name: MaxTemp, dtype: int64
Index(['Mon', 'Tue', 'Wed', 'Thu'], dtype='object', name='Day')
[41 39 44 40]
int64 (4,) 4 1
False True
Teacher tip

size counts NaN values; the method count() does not. s.shape is a tuple with one number, written (4,) with a comma.

Accessing elements: labels and positions (loc and iloc)

Every element of a Series has a label (from the index) and a position (0, 1, 2, ... from the left; -1 from the right).

  • s['Pune'] or s.loc['Pune'] reads by label.
  • s.iloc[2] reads by position.
  • A list inside the brackets returns several elements: s.loc[['Pune', 'Agra']], s.iloc[[0, 3]].
  • Assignment works the same way: s.loc['Pune'] = 50.

Version warning. Older books write s[1] to mean "second element" even when the labels are words. Pandas 2.x allows this with a warning, but Pandas 3 treats s[1] as the label 1 and raises KeyError. Teach s.iloc[1] for positions; it works in every version.

Python
import pandas as pd
rain = pd.Series([722, 2955, 603, 1092], index=['Pune', 'Cherrapunji', 'Agra', 'Bhopal'])
print(rain['Agra'])
print(rain.loc['Pune'])
print(rain.iloc[1])
print(rain.iloc[-1])
print(rain.loc[['Agra', 'Pune']])
print(rain.iloc[[0, 3]])
Output
603
722
2955
1092
Agra    603
Pune    722
dtype: int64
Pune       722
Bhopal    1092
dtype: int64
Teacher tip

When the index is made of integers (for example roll numbers 101, 102, ...), s[101] is a label lookup and s[0] is a KeyError. Using loc and iloc removes all doubt.

Slicing a Series

A slice returns a part of the Series as a new Series.

  • Position slices s[start:stop] or s.iloc[start:stop] work like list slices: the stop position is excluded.
  • Label slices s['b':'d'] or s.loc['b':'d'] include both ends.
  • A step works as usual: s[::-1] reverses, s[::2] takes every second element.
  • A slice can be used on the left of = to change several values at once.
ExpressionKindElements returned
s[1:3]positionb, c (position 3 excluded)
s.iloc[1:3]positionb, c
s['b':'d']labelb, c, d (end label included)
s.loc['b':'d']labelb, c, d
s[-2:]positiond, e
Python
import pandas as pd
s = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e'])
print(s[1:3])
print(s['b':'d'])
print(s.iloc[::-2])
s['c':'d'] = 0
print(s.tolist())
Output
b    20
c    30
dtype: int64
b    20
c    30
d    40
dtype: int64
e    50
c    30
a    10
dtype: int64
[10, 20, 0, 0, 50]
Teacher tip

Board examiners love s[1:3] versus s['b':'d']. Get students to say "positions exclude, labels include" aloud.

Boolean filtering of a Series

A comparison such as s > 40 gives a Series of True/False values. Putting it inside the brackets keeps only the elements where the value is True. This is called Boolean indexing or filtering.

Combine conditions with & (and), | (or) and ~ (not). Each condition must be in brackets, because & and | are evaluated before > and <. The Python words and, or and not do not work on a Series.

Python
import pandas as pd
temp = pd.Series([38, 44, 41, 36, 45], index=['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
print(temp > 40)
print(temp[temp > 40])
print(temp[(temp > 37) & (temp < 42)])
print(temp[~(temp > 40)].index.tolist())
Output
Mon    False
Tue     True
Wed     True
Thu    False
Fri     True
dtype: bool
Tue    44
Wed    41
Fri    45
dtype: int64
Mon    38
Wed    41
dtype: int64
['Mon', 'Thu']

head(), tail() and count() on a Series

  • s.head(n) returns the first n elements; s.tail(n) returns the last n. With no argument, n = 5.
  • If n is more than the length, the whole Series is returned (no error).
  • s.count() returns the number of non-NaN values, unlike size, which counts all.

These are handy for a quick look at long data, such as 300 students' marks.

Python
import pandas as pd
import numpy as np
s = pd.Series([12, 25, np.nan, 31, 47, 58, 66])
print(s.head())
print(s.tail(2))
print(s.size, s.count())
Output
0    12.0
1    25.0
2     NaN
3    31.0
4    47.0
dtype: float64
5    58.0
6    66.0
dtype: float64
7 6

Mathematical operations on a Series

With a scalar, an operator is applied to every element (vectorised): s * 2, s + 5, s ** 2, s % 3. No loop is needed.

Between two Series, Pandas matches elements by index label, not by position. This is called alignment:

  • Labels present in both Series are combined.
  • A label present in only one Series gives NaN in the result.
  • The result's index is the sorted union of both indexes, and the dtype becomes float64 if any NaN appears.

To treat a missing value as a number instead, use the methods add(), sub(), mul() and div() with fill_value. The fill value stands in for the missing side only; a label missing on both sides still gives NaN.

OperatorMethodWith fill_value
s1 + s2s1.add(s2)s1.add(s2, fill_value=0)
s1 - s2s1.sub(s2)s1.sub(s2, fill_value=0)
s1 * s2s1.mul(s2)s1.mul(s2, fill_value=1)
s1 / s2s1.div(s2)s1.div(s2, fill_value=1)
Python
import pandas as pd
shopA = pd.Series([40, 25, 60], index=['pen', 'pencil', 'notebook'])
shopB = pd.Series([35, 50, 20], index=['notebook', 'eraser', 'pen'])
print(shopA * 2)
print(shopA + shopB)
print(shopA.add(shopB, fill_value=0))
print(shopA.sub(shopB, fill_value=0)['eraser'])
Output
pen          80
pencil       50
notebook    120
dtype: int64
eraser       NaN
notebook    95.0
pen         60.0
pencil       NaN
dtype: float64
eraser      50.0
notebook    95.0
pen         60.0
pencil      25.0
dtype: float64
-50.0
Teacher tip

When the two Series have the same labels in a different order, the result still matches pen with pen. Ask students to predict the output before running it; most will add position by position at first.

Creating a DataFrame from a dictionary of lists, a list of lists or an ndarray

A DataFrame is a table: pd.DataFrame(data, index=..., columns=...).

  • Dictionary of lists: each key becomes a column label and each list becomes that column. All lists must have the same length.
  • List of lists (or a 2-D ndarray): each inner list becomes a row. Give column labels with columns=.
  • index= sets the row labels; otherwise rows are numbered 0, 1, 2, ...
  • Passing columns= with a dictionary chooses and orders the columns.
  • pd.DataFrame() with no data gives an empty DataFrame.
Python
import pandas as pd
data = {'Name': ['Aarav', 'Diya', 'Kabir'],
        'Maths': [78, 91, 65],
        'Science': [82, 88, 70]}
df = pd.DataFrame(data, index=['S1', 'S2', 'S3'])
print(df)
print()
rows = [[12951, 'Rajdhani', 'Mumbai'], [12301, 'Rajdhani', 'Howrah'], [22439, 'Vande Bharat', 'Katra']]
trains = pd.DataFrame(rows, columns=['TrainNo', 'Type', 'To'])
print(trains)
print(pd.DataFrame().empty)
Output
     Name  Maths  Science
S1  Aarav     78       82
S2   Diya     91       88
S3  Kabir     65       70

   TrainNo          Type      To
0    12951      Rajdhani  Mumbai
1    12301      Rajdhani  Howrah
2    22439  Vande Bharat   Katra
True

Creating a DataFrame from a list of dictionaries

Each dictionary is one row. The keys become column labels. If a dictionary does not have a key that others have, that cell is NaN, and an integer column with NaN becomes float64.

This form is natural when records arrive one at a time, for example one dictionary per student filling a form.

Python
import pandas as pd
records = [{'Name': 'Ananya', 'House': 'Tagore', 'Points': 18},
           {'Name': 'Vihaan', 'House': 'Raman'},
           {'Name': 'Sara', 'House': 'Tagore', 'Points': 25}]
df = pd.DataFrame(records)
print(df)
print(df.dtypes)
Output
     Name   House  Points
0  Ananya  Tagore    18.0
1  Vihaan   Raman     NaN
2    Sara  Tagore    25.0
Name       object
House      object
Points    float64
dtype: object

Creating a DataFrame from a dictionary of Series

Each Series becomes a column and the dictionary key is its label. The rows are lined up by the index labels of the Series (alignment again):

  • The row index of the DataFrame is the union of all the Series' indexes.
  • Where a Series has no value for a label, the cell is NaN.

A single Series can also be turned into a one-column DataFrame with pd.DataFrame(s) or s.to_frame().

Python
import pandas as pd
maths = pd.Series([78, 91, 65], index=['Aarav', 'Diya', 'Kabir'])
hindi = pd.Series([88, 72, 69], index=['Diya', 'Kabir', 'Meera'])
df = pd.DataFrame({'Maths': maths, 'Hindi': hindi})
print(df)
Output
       Maths  Hindi
Aarav   78.0    NaN
Diya    91.0   88.0
Kabir   65.0   72.0
Meera    NaN   69.0
Teacher tip

Compare this with a dictionary of lists, where rows are matched only by position. Here Diya's Hindi mark lands in Diya's row even though it is first in its Series.

Displaying a DataFrame; head() and tail()

print(df) shows the whole table with row labels on the left. For a long table Pandas shows the first and last few rows with ... between them.

As with a Series, df.head(n) gives the first n rows and df.tail(n) the last n rows, with n = 5 by default. A negative n in head(-2) means "all rows except the last 2".

Python
import pandas as pd
df = pd.read_csv('marks.csv')
print(df.head(3))
print()
print(df.tail(2))
print()
print(df.head(-3))
Output
   RollNo   Name  Maths  Science  English
0       1  Aarav     78       82       74
1       2   Diya     91       88       95
2       3  Kabir     65       70       60

   RollNo   Name  Maths  Science  English
3       4  Meera     88       79       85
4       5  Rohan     55       62       58

   RollNo   Name  Maths  Science  English
0       1  Aarav     78       82       74
1       2   Diya     91       88       95

Attributes of a DataFrame

Attributes describe the table without changing it. CBSE practical 4 asks students to show the row labels, column labels, data types of each column and the dimensions of an exam-result DataFrame.

AttributeGives
df.indexRow labels
df.columnsColumn labels
df.dtypesData type of each column (a Series)
df.valuesAll data as a 2-D NumPy array
df.shapeTuple (number of rows, number of columns)
df.sizeTotal number of cells = rows × columns
df.ndimNumber of dimensions (2)
df.TTranspose: rows become columns and columns become rows
df.emptyTrue if the DataFrame has no cells
Python
import pandas as pd
df = pd.DataFrame({'Name': ['Ishaan', 'Tanvi', 'Farhan'],
                   'Section': ['A', 'B', 'A'],
                   'Percent': [86.4, 92.0, 74.5]},
                  index=['A101', 'A102', 'A103'])
print(df.index)
print(df.columns)
print(df.dtypes)
print(df.shape, df.size, df.ndim, df.empty)
print(df.values)
print(df.T)
Output
Index(['A101', 'A102', 'A103'], dtype='object')
Index(['Name', 'Section', 'Percent'], dtype='object')
Name        object
Section     object
Percent    float64
dtype: object
(3, 3) 9 2 False
[['Ishaan' 'A' 86.4]
 ['Tanvi' 'B' 92.0]
 ['Farhan' 'A' 74.5]]
           A101   A102    A103
Name     Ishaan  Tanvi  Farhan
Section       A      B       A
Percent    86.4   92.0    74.5
Teacher tip

df.size is rows × columns, not the number of rows. For 40 students and 6 columns the answer is 240. len(df) gives the number of rows.

Selecting columns and rows

Columns

  • df['Maths'] returns one column as a Series.
  • df.Maths does the same, but only when the label is a valid Python name (no spaces, not a method name like count).
  • df[['Name', 'Maths']] (a list inside the brackets) returns a DataFrame with those columns.

Rows

  • df.loc['S2'] returns the row labelled S2 as a Series; df.iloc[1] returns the second row.
  • df.loc[['S1', 'S3']] returns several rows as a DataFrame.

Single cell: df.loc['S2', 'Maths'] or df.iloc[1, 1].

Note: df['S2'] looks for a column called S2, so it raises KeyError for a row label.

Python
import pandas as pd
df = pd.DataFrame({'Name': ['Aarav', 'Diya', 'Kabir'],
                   'Maths': [78, 91, 65],
                   'Science': [82, 88, 70]}, index=['S1', 'S2', 'S3'])
print(df['Maths'])
print(df[['Name', 'Science']])
print(df.loc['S2'])
print(df.loc['S2', 'Maths'], df.iloc[2, 2])
Output
S1    78
S2    91
S3    65
Name: Maths, dtype: int64
     Name  Science
S1  Aarav       82
S2   Diya       88
S3  Kabir       70
Name       Diya
Maths        91
Science      88
Name: S2, dtype: object
91 70

Slicing a DataFrame with loc and iloc

Both accessors take [rows, columns]. Each part can be a single label/position, a list, or a slice.

  • df.loc['S2':'S4', 'Maths':'English']: label slices, both ends included.
  • df.iloc[1:3, 0:2]: position slices, end excluded.
  • : alone means "all": df.loc[:, 'Maths'] is the whole Maths column.
  • df[1:3] with a plain slice selects rows by position.

Slices can also be assigned: df.loc['S2':'S3', 'Maths'] = 0.

Python
import pandas as pd
df = pd.read_csv('marks.csv', index_col='RollNo')
print(df.loc[2:4, 'Maths':'English'])
print()
print(df.iloc[1:3, 0:2])
print()
print(df[0:2])
Output
        Maths  Science  English
RollNo                         
2          91       88       95
3          65       70       60
4          88       79       85

         Name  Maths
RollNo              
2        Diya     91
3       Kabir     65

         Name  Maths  Science  English
RollNo                                
1       Aarav     78       82       74
2        Diya     91       88       95
Teacher tip

Here the row labels are roll numbers 1 to 5, so df.loc[2:4] returns rolls 2, 3 and 4 (labels, inclusive) while df.iloc[2:4] returns rolls 3 and 4 (positions 2 and 3). Make students compare the two.

Boolean indexing on a DataFrame

A condition on a column gives a Boolean Series, one value per row. Using it inside df[...] keeps only the rows where it is True.

  • df[df['Maths'] >= 75]: rows with Maths at least 75.
  • df.loc[df['Maths'] >= 75, ['Name', 'Maths']]: same rows, chosen columns.
  • Combine with &, |, ~ and brackets around each condition.
  • df['Name'].isin([...]) tests membership in a list.

Duplicate rows (CBSE practical 5): df.duplicated() marks a row True if an identical row appeared earlier, so df[df.duplicated()] shows the repeats and df[~df.duplicated()] (or df.drop_duplicates()) keeps one copy of each. Pass subset=['col'] to compare only some columns.

Python
import pandas as pd
df = pd.read_csv('marks.csv', index_col='RollNo')
print(df[df['Maths'] >= 75])
print()
print(df.loc[(df.Science > 80) | (df.English < 60), ['Name', 'Science', 'English']])
print()
entries = pd.DataFrame({'Name': ['Riya', 'Arjun', 'Riya', 'Neha'],
                        'Event': ['100m', 'Chess', '100m', 'Chess']})
print(entries[entries.duplicated()])
print(entries[~entries.duplicated(subset=['Event'])])
Output
         Name  Maths  Science  English
RollNo                                
1       Aarav     78       82       74
2        Diya     91       88       95
4       Meera     88       79       85

         Name  Science  English
RollNo                         
1       Aarav       82       74
2        Diya       88       95
5       Rohan       62       58

   Name Event
2  Riya  100m
    Name  Event
0   Riya   100m
1  Arjun  Chess

Adding columns and rows

Add a column by assigning to a new label: df['Total'] = .... The right side can be a list (one value per row), a scalar (same value in every row) or a calculation on other columns. Assigning to an existing label replaces that column. df.insert(pos, 'label', values) puts a new column at a chosen position.

Add a row with loc and a new label: df.loc['S4'] = [...]. With the default numeric index, df.loc[len(df)] = [...] adds a row at the end. If the label already exists, that row is overwritten.

To add the rows of another DataFrame, use pd.concat([df, more]). The old df.append() method was removed in Pandas 2.0, so do not teach it.

Python
import pandas as pd
df = pd.DataFrame({'Name': ['Aarav', 'Diya'], 'Maths': [78, 91], 'Science': [82, 88]})
df['Total'] = df['Maths'] + df['Science']
df['Class'] = 'XII-A'
df.loc[len(df)] = ['Kabir', 65, 70, 135, 'XII-A']
df.insert(1, 'Roll', [1, 2, 3])
print(df)
Output
    Name  Roll  Maths  Science  Total  Class
0  Aarav     1     78       82    160  XII-A
1   Diya     2     91       88    179  XII-A
2  Kabir     3     65       70    135  XII-A
Teacher tip

A list of the wrong length gives ValueError: Length of values does not match length of index. When adding a row with loc, the list must have one value per column.

Deleting rows and columns: drop() and del

df.drop(labels, axis=...) returns a new DataFrame without the given rows or columns.

  • axis=0 (the default) means rows; axis=1 means columns.
  • The clearer keywords df.drop(index=[...]) and df.drop(columns=[...]) do the same job.
  • The original is unchanged unless you assign the result back: df = df.drop(...).
  • A label that does not exist raises KeyError.

Older books and many board papers write df.drop(..., inplace=True), which changes df itself and returns None. It works in Pandas 1.x, 2.x and 3.0, but Pandas 3.1 has deprecated inplace, so teach df = df.drop(...) as the main form and make sure students can still read the inplace form.

del df['col'] deletes one column from the DataFrame itself. df.pop('col') deletes a column and returns it as a Series.

Python
import pandas as pd
df = pd.read_csv('marks.csv', index_col='RollNo')
print(df.drop(3))
print()
print(df.drop(['Science', 'English'], axis=1).columns.tolist())
df = df.drop(index=[1, 2])
del df['English']
print(df)
Output
         Name  Maths  Science  English
RollNo                                
1       Aarav     78       82       74
2        Diya     91       88       95
4       Meera     88       79       85
5       Rohan     55       62       58

['Name', 'Maths']
         Name  Maths  Science
RollNo                       
3       Kabir     65       70
4       Meera     88       79
5       Rohan     55       62
Teacher tip

The most common error is df.drop('Science') without axis=1. Pandas looks for a row called Science and raises KeyError.

Renaming rows and columns

df.rename(index={old: new}, columns={old: new}) changes only the labels named in the dictionaries; labels not mentioned stay as they are, and a label that does not exist is ignored silently. Like drop(), it returns a new DataFrame, so assign it back (or, in older code, pass inplace=True).

To replace all labels at once, assign a list of the right length: df.columns = [...] or df.index = [...].

Python
import pandas as pd
df = pd.DataFrame({'nm': ['Gill', 'Jadeja'], 'r': [91, 44], 'b': [102, 38]})
df = df.rename(columns={'nm': 'Player', 'r': 'Runs', 'b': 'Balls'})
df = df.rename(index={0: 'P1', 1: 'P2'})
print(df)
df.columns = ['Batter', 'R', 'B']
print(df.columns.tolist())
Output
    Player  Runs  Balls
P1    Gill    91    102
P2  Jadeja    44     38
['Batter', 'R', 'B']

Iterating over a DataFrame: iterrows() and items()

  • df.iterrows() goes row by row. Each step gives a pair (row_label, row), where row is a Series indexed by the column labels.
  • df.items() goes column by column, giving (column_label, column) pairs.

Older books use iteritems(); it was removed in Pandas 2.0, so use items(), which works in every version.

Loops are slow on big data. Use them for printing reports; for calculations prefer column operations like df['Maths'] * 2.

Python
import pandas as pd
df = pd.DataFrame({'Name': ['Aarav', 'Diya'], 'Maths': [78, 91], 'Science': [82, 88]})
for label, row in df.iterrows():
    print(label, row['Name'], row['Maths'] + row['Science'])
for col, values in df.items():
    print(col, values.tolist())
Output
0 Aarav 160
1 Diya 179
Name ['Aarav', 'Diya']
Maths [78, 91]
Science [82, 88]

Importing a CSV file into a DataFrame: read_csv()

A CSV (comma-separated values) file is a plain text file where each line is a row and commas separate the values. Spreadsheets and databases can save data as CSV.

pd.read_csv('file.csv', ...) reads it into a DataFrame. Useful parameters:

  • sep: the separator, ',' by default; use sep=';' or sep='\t' for other files.
  • header: the row number holding column labels (0 by default). Use header=None when the file has no heading row; columns are then numbered 0, 1, 2, ...
  • names: a list of column labels to use (usually with header=None).
  • index_col: a column (name or number) to use as the row labels.
  • nrows: read only the first n data rows.

If the file is not in the same folder as the program, give its full path, e.g. 'C:/data/marks.csv' (forward slashes avoid backslash escape problems).

Python
import pandas as pd
df = pd.read_csv('marks.csv', index_col='RollNo', nrows=3)
print(df)
print()
st = pd.read_csv('stations.txt', sep=';', header=None,
                 names=['Code', 'Station', 'State', 'Platforms'], index_col='Code')
print(st)
Output
         Name  Maths  Science  English
RollNo                                
1       Aarav     78       82       74
2        Diya     91       88       95
3       Kabir     65       70       60

              Station        State  Platforms
Code                                         
NDLS        New Delhi        Delhi         16
BCT    Mumbai Central  Maharashtra          9
MAS   Chennai Central   Tamil Nadu         17
HWH            Howrah  West Bengal         23
Teacher tip

Show the raw file in Notepad first. marks.csv has a heading line RollNo,Name,Maths,Science,English; stations.txt has no heading and uses semicolons, e.g. NDLS;New Delhi;Delhi;16.

Exporting a DataFrame to a CSV file: to_csv()

df.to_csv('file.csv', ...) writes the DataFrame to a text file (it overwrites an existing file of the same name). Useful parameters:

  • sep: separator to write, ',' by default.
  • header: True (default) writes the column labels; False leaves them out.
  • index: True (default) writes the row labels as the first column; False leaves them out. Forgetting index=False is why an extra unnamed column appears when the file is read back.
  • na_rep: text to write for NaN (an empty field by default).

Called without a file name, to_csv() returns the CSV text as a string, which is handy for checking.

Python
import pandas as pd
df = pd.read_csv('result.csv')
top = df[df['Percent'] >= 85]
top.to_csv('toppers.csv', index=False)
print(open('toppers.csv').read())
print(top.to_csv(sep='|', header=False))
Output
Adm,Name,Class,Percent
A101,Ishaan,12A,86.4
A102,Tanvi,12B,92.0
A106,Pooja,12A,95.6

0|A101|Ishaan|12A|86.4
1|A102|Tanvi|12B|92.0
5|A106|Pooja|12A|95.6

Joining DataFrames with pd.concat() (Enrichment)

pd.concat([df1, df2]) stacks DataFrames one below the other, matching columns by label. Columns missing in one DataFrame are filled with NaN. Pass ignore_index=True for a fresh 0, 1, 2, ... index, and axis=1 to place them side by side.

This replaces the DataFrame.append() method shown in older books, which no longer exists in Pandas 2.0 and later.

Python
import pandas as pd
secA = pd.DataFrame({'Name': ['Ira', 'Dev'], 'Marks': [81, 77]})
secB = pd.DataFrame({'Name': ['Zoya'], 'Marks': [90], 'Club': ['Robotics']})
print(pd.concat([secA, secB], ignore_index=True))
Output
   Name  Marks      Club
0   Ira     81       NaN
1   Dev     77       NaN
2  Zoya     90  Robotics

Key terms

Library
A collection of ready-made modules that can be imported into a program, such as NumPy, Pandas and Matplotlib.
Pandas
A Python library for analysing labelled, tabular data; its name comes from 'panel data'.
Series
A one-dimensional labelled array in which all values share one data type.
DataFrame
A two-dimensional labelled table of rows and columns; each column is a Series and columns may have different data types.
Index
The labels attached to the rows of a Series or DataFrame.
Column label
The name of a DataFrame column, stored in df.columns.
ndarray
NumPy's N-dimensional array of values of a single type; it can be used to create a Series or DataFrame.
NaN
'Not a Number'; the floating-point marker Pandas uses for a missing value.
dtype
The data type of a Series or column, e.g. int64, float64, object.
Attribute
A property of an object read without brackets, e.g. df.shape.
Alignment
Matching elements by index label (not by position) when two Series or DataFrames are combined.
loc
Accessor that selects rows and columns by label; label slices include the end.
iloc
Accessor that selects rows and columns by integer position; slices exclude the end.
Slicing
Selecting a continuous range of elements or rows using start:stop:step.
Boolean indexing
Selecting the elements or rows where a True/False condition is True.
Transpose
Swapping rows and columns of a DataFrame, obtained with df.T.
axis
Direction of an operation: axis=0 means rows (index), axis=1 means columns.
inplace
A parameter that, when True, changes the original object instead of returning a new one; deprecated for most methods from Pandas 3.1.
CSV file
A plain text file in which each line is a record and values are separated by a delimiter, usually a comma.
Delimiter
The character that separates values in a text file, set with sep in read_csv() and to_csv().

MCQs

Tap an option to check it, or use Show answers above to reveal them all.

  1. Which Python library provides the Series and DataFrame data structures?

    Answer

    (c) Pandas

    Series and DataFrame are the two main data structures of Pandas. NumPy provides ndarray; Matplotlib is for plotting.

  2. A Series is best described as:

    Answer

    (a) a one-dimensional labelled array of values of one data type

    A Series is one-dimensional and has an index (labels). The table description fits a DataFrame.

  3. Which statement creates a Series of four elements, each equal to 100?

    Answer

    (d) pd.Series(100, index=['a', 'b', 'c', 'd'])

    A scalar value is repeated once for every label in index. pd.Series(100) makes only one element.

  4. What is printed?

    print(len(pd.Series({'Goa': 2, 'Sikkim': 1, 'Bihar': 9})))

    Answer

    (b) 3

    The three keys become three index labels, so the Series has 3 elements.

  5. s = pd.Series({'a': 1, 'b': 2}, index=['b', 'c']). What are the values of s?

    Answer

    (b) [2, nan]

    Only the labels in index are used. 'b' gets 2 and 'c' is not a key, so it gets NaN.

  6. s = pd.Series([3, 6, 9, 12]). What is printed by print(list(s.index))?

    Answer

    (c) [0, 1, 2, 3]

    With no index given, Pandas numbers the elements from 0.

  7. s = pd.Series([5, 10, 15, 20, 25], index=list('pqrst')). How many elements does s['q':'s'] return?

    Answer

    (b) 3

    A label slice includes both ends: q, r and s.

  8. For the same Series s (labels p to t), which labels does s[1:3] return?

    Answer

    (a) q and r

    An integer slice works on positions and excludes the stop position 3, so positions 1 and 2 (q and r) are returned.

  9. Which Series attribute gives the total number of elements, including NaN values?

    Answer

    (d) size

    size counts every element. The method count() skips NaN; ndim is always 1 for a Series; shape has only one item.

  10. What is printed by print(pd.Series(dtype='float64').empty)?

    Answer

    (a) True

    The Series has no elements, so empty is True.

  11. When two Series are added with +, what appears for a label present in only one of them?

    Answer

    (c) NaN

    Addition aligns on labels. A label missing from one side has nothing to add to, so the result is NaN. Use add(..., fill_value=0) to avoid it.

  12. s1 = pd.Series([1, 2, 3], index=['x', 'y', 'z']) and s2 = pd.Series([4, 5], index=['y', 'z']). What is (s1 + s2)['z']?

    Answer

    (b) 8.0

    z is in both: 3 + 5 = 8. The result is float because label x gives NaN, which makes the whole Series float64.

  13. s = pd.Series([3, 6, 9, 12, 15, 18]). What is printed by print(s.tail(2).tolist())?

    Answer

    (b) [15, 18]

    tail(2) returns the last two elements.

  14. How many rows does df.head() return for a DataFrame with 50 rows?

    Answer

    (d) 5

    The default value of n in head() and tail() is 5.

  15. Which works in every Pandas version to get the first element of a Series whose index labels are names of cities?

    Answer

    (a) s.iloc[0]

    iloc always uses positions. s[0] treats 0 as a label in Pandas 3 and raises KeyError; s.loc[0] always looks for label 0.

  16. s = pd.Series([12, 45, 7, 60, 33]). What is printed by print(s[s > 30].tolist())?

    Answer

    (b) [45, 60, 33]

    Boolean indexing keeps the values greater than 30, in their original order.

  17. A DataFrame is created from a list of dictionaries. One dictionary has no key 'Phone' that the others have. What is stored in that cell?

    Answer

    (c) NaN

    Missing keys become NaN in the DataFrame.

  18. A DataFrame df has 4 rows and 3 columns. What is printed by print(df.shape, df.size)?

    Answer

    (d) (4, 3) 12

    shape is (rows, columns) and size is rows × columns = 12.

  19. Which statement permanently removes the column 'Fees' from df?

    Answer

    (c) df = df.drop('Fees', axis=1)

    axis=1 points to columns, and assigning the result back makes the change permanent. The older form df.drop('Fees', axis=1, inplace=True) does the same. The first two options look for a row called Fees; there is no remove() method.

  20. Which method lets you loop over a DataFrame row by row, getting the row label and the row as a Series?

    Answer

    (b) iterrows()

    iterrows() yields (label, row) pairs. items() loops over columns; iteritems() was removed in Pandas 2.0.

  21. What does df.T return?

    Answer

    (a) The transpose of df

    T swaps rows and columns.

  22. Which read_csv() parameter makes a column of the file the row labels of the DataFrame?

    Answer

    (c) index_col

    index_col='RollNo' (or a column number) uses that column as the index.

  23. Which to_csv() call writes the file without row labels?

    Answer

    (b) df.to_csv('a.csv', index=False)

    index=False leaves out the row labels; header=False leaves out the column labels.

  24. df has row labels 'R1' to 'R6'. How many rows does df.loc['R2':'R4'] return?

    Answer

    (d) 3

    loc slices include the end label: R2, R3 and R4.

  25. Which statement renames the column 'Amt' to 'Amount' and leaves other columns unchanged?

    Answer

    (a) df.rename(columns={'Amt': 'Amount'})

    The columns dictionary maps old label to new label. index= renames rows; assigning df.columns must list every column.

Assertion–Reason

Choose: (A) Both A and R are true and R is the correct explanation of A. (B) Both A and R are true but R is not the correct explanation of A. (C) A is true but R is false. (D) A is false but R is true.
  1. Assertion (A): pd.Series([10, 20, 30], index=['a', 'b']) raises an error.

    Reason (R): When an index is given with a list of values, the number of labels must equal the number of values.

    Answer

    (A)

    Two labels cannot be matched to three values, so Pandas raises ValueError, exactly as R explains.

  2. Assertion (A): For s = pd.Series([1, 2, 3, 4], index=['w', 'x', 'y', 'z']), s['x':'z'] returns three elements.

    Reason (R): Slicing a Series with labels excludes the stop label.

    Answer

    (C)

    A is true (x, y, z). R is false: label slices include the stop label; only position slices exclude it.

  3. Assertion (A): Adding two Series that have different index labels can give a result with dtype float64 even if both Series hold integers.

    Reason (R): Labels that do not match produce NaN, and NaN is a floating-point value.

    Answer

    (A)

    The NaN values force the result to float64, which is the reason R gives.

  4. Assertion (A): df.drop('Marks') removes the column Marks from df.

    Reason (R): By default drop() works along axis=0, that is, on row labels.

    Answer

    (D)

    A is false: with the default axis Pandas looks for a row called Marks and raises KeyError. R is true.

  5. Assertion (A): df.size of a DataFrame with 30 rows and 5 columns is 150.

    Reason (R): size gives the number of rows of a DataFrame.

    Answer

    (C)

    A is true (30 × 5). R is false: size counts all cells; len(df) or df.shape[0] gives the number of rows.

  6. Assertion (A): df.items() can be used to loop over the columns of a DataFrame.

    Reason (R): iterrows() returns each row as a Series.

    Answer

    (B)

    Both are true, but R describes a different method and does not explain A.

Short answer

  1. 2 marks

    Give two differences between a Pandas Series and a NumPy ndarray.

    Answer
    1. A Series has an index of labels (which can be text), while an ndarray is accessed only by integer positions.
    2. When two Series are added, values are matched by label and unmatched labels give NaN; two ndarrays are added position by position and must have compatible shapes.
  2. 2 marks

    Write Python code to create a Series fees holding 2500 for each of the months 'Apr', 'May' and 'Jun'.

    Answer

    A scalar is repeated for every label in the index.

    Python
    import pandas as pd
    fees = pd.Series(2500, index=['Apr', 'May', 'Jun'])
    print(fees)
    Output
    Apr    2500
    May    2500
    Jun    2500
    dtype: int64
  3. 2 marks

    Write code to create a Series from the dictionary {'Mumbai': 12.4, 'Delhi': 16.8, 'Kolkata': 4.5} (population in millions) and print its index and values.

    Answer

    The keys become the index and the values become the data.

    Python
    import pandas as pd
    pop = pd.Series({'Mumbai': 12.4, 'Delhi': 16.8, 'Kolkata': 4.5})
    print(pop.index)
    print(pop.values)
    Output
    Index(['Mumbai', 'Delhi', 'Kolkata'], dtype='object')
    [12.4 16.8  4.5]
  4. 2 marks

    Write the output:

    s = pd.Series([12, 24, 36, 48, 60], index=['p', 'q', 'r', 's', 't'])
    print(s[1:4])
    print(s['q':'s'])

    Answer

    Both give the same three elements: the position slice stops before position 4, and the label slice includes 's'.

    Text
    q    24
    r    36
    s    48
    dtype: int64
    q    24
    r    36
    s    48
    dtype: int64
  5. 2 marks

    Write the output:

    s = pd.Series([4, 8, 12, 16], index=['a', 'b', 'c', 'd'])
    print(s * 2 - 1)
    print(s[s % 8 == 0])

    Answer

    Arithmetic with a scalar works on every element; the filter keeps multiples of 8.

    Text
    a     7
    b    15
    c    23
    d    31
    dtype: int64
    b     8
    d    16
    dtype: int64
  6. 2 marks

    Write the output:

    a = pd.Series([10, 20, 30], index=['Mon', 'Tue', 'Wed'])
    b = pd.Series([5, 15], index=['Tue', 'Thu'])
    print(a + b)

    Answer

    Only Tue is in both Series. The result index is the sorted union of labels, and every unmatched label gives NaN.

    Text
    Mon     NaN
    Thu     NaN
    Tue    25.0
    Wed     NaN
    dtype: float64
  7. 2 marks

    For the Series a and b of the previous question, write the output of print(a.add(b, fill_value=0)).

    Answer

    The missing side is taken as 0, so no NaN remains.

    Text
    Mon    10.0
    Thu    15.0
    Tue    25.0
    Wed    30.0
    dtype: float64
  8. 2 marks

    Differentiate between loc and iloc with one example each.

    Answer

    loc selects by label, and a loc slice includes the end label: df.loc['S1':'S3', 'Maths'] gives rows S1, S2 and S3.

    iloc selects by integer position, and an iloc slice excludes the end position: df.iloc[0:2, 1] gives the first two rows of the second column.

  9. 2 marks

    Write the output:

    s = pd.Series([90, 75, 82, 68, 95, 71], index=list('ABCDEF'))
    print(s.head(2))
    print(s.tail(-4))

    Answer

    head(2) gives the first two elements. A negative n in tail(-4) means all elements except the first 4.

    Text
    A    90
    B    75
    dtype: int64
    E    95
    F    71
    dtype: int64
  10. 2 marks

    Name any four attributes of a DataFrame and state what each returns.

    Answer
    • index: row labels
    • columns: column labels
    • dtypes: data type of each column
    • shape: (rows, columns) as a tuple

    (Others: values, size, T, empty, ndim.)

  11. 2 marks

    Write the output:

    df = pd.DataFrame({'Item': ['Pen', 'Ink', 'Pad'], 'Qty': [10, 4, 7]}, index=['i1', 'i2', 'i3'])
    print(df.loc['i2':'i3', 'Qty'])
    print(df.iloc[0, 0])

    Answer

    Selecting one column with a row slice gives a Series named Qty; iloc[0, 0] is the first cell.

    Text
    i2    4
    i3    7
    Name: Qty, dtype: int64
    Pen
  12. 2 marks

    What is the difference between del df['Bonus'] and df.drop('Bonus', axis=1)?

    Answer

    del df['Bonus'] deletes the column from df itself and returns nothing; it works on one column only.

    df.drop('Bonus', axis=1) returns a new DataFrame without the column and leaves df unchanged unless the result is assigned back (or inplace=True is passed in older code). It can also remove several columns or rows at once.

  13. 2 marks

    Write code to read stock.csv, which has no heading row and uses | as the separator, giving the columns the labels Code, Item and Qty.

    Answer

    Use header=None because the file has no heading row, and names to supply labels. For a file containing lines such as S01|Geometry box|40:

    Python
    import pandas as pd
    df = pd.read_csv('stock.csv', sep='|', header=None, names=['Code', 'Item', 'Qty'])
    print(df)
    Output
      Code           Item  Qty
    0  S01   Geometry box   40
    1  S02  Drawing sheet  250
    2  S03     Glue stick   75
  14. 2 marks

    Why does an extra column such as Unnamed: 0 often appear when a DataFrame saved with to_csv() is read back? How do you prevent it?

    Answer

    By default to_csv() writes the row labels as the first column. When the file is read back, read_csv() treats it as an ordinary column with a blank heading, named Unnamed: 0.

    Prevent it by writing with df.to_csv('f.csv', index=False), or read it back with index_col=0.

  15. 2 marks

    Write the output:

    df = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['x', 'y'])
    df['z'] = df['x'] * df['y']
    print(df.T)

    Answer

    Column z is added first; the transpose then turns the columns x, y, z into rows.

    Text
       0   1   2
    x  1   3   5
    y  2   4   6
    z  2  12  30

Long answer

  1. 3 marks

    Create a Series marks from the NumPy array [67, 88, 94, 72, 55] with index ['Riya', 'Om', 'Liza', 'Jai', 'Esha']. Then write statements to:

    1. print the marks of Liza,
    2. print the first three students,
    3. print students who scored more than 70,
    4. add 5 grace marks to every student who scored below 60.
    Answer

    Use loc for labels, head() or a slice for the first three, and a Boolean condition both for filtering and for updating.

    Python
    import pandas as pd
    import numpy as np
    marks = pd.Series(np.array([67, 88, 94, 72, 55]), index=['Riya', 'Om', 'Liza', 'Jai', 'Esha'])
    print(marks.loc['Liza'])
    print(marks.head(3))
    print(marks[marks > 70])
    marks[marks < 60] = marks[marks < 60] + 5
    print(marks)
    Output
    94
    Riya    67
    Om      88
    Liza    94
    dtype: int64
    Om      88
    Liza    94
    Jai     72
    dtype: int64
    Riya    67
    Om      88
    Liza    94
    Jai     72
    Esha    60
    dtype: int64
  2. 5 marks

    Explain with examples three ways of creating a DataFrame: from a dictionary of lists, from a list of dictionaries and from a dictionary of Series. Show how missing data is handled in each.

    Answer

    Dictionary of lists: keys become columns; all lists must be the same length, so there is no missing data (a short list raises ValueError).

    List of dictionaries: each dictionary is a row; a key missing in one dictionary gives NaN in that row.

    Dictionary of Series: each Series is a column and rows are matched by index labels; a label missing in one Series gives NaN.

    Python
    import pandas as pd
    d1 = pd.DataFrame({'City': ['Leh', 'Kochi'], 'Temp': [-4, 31]})
    d2 = pd.DataFrame([{'City': 'Leh', 'Temp': -4}, {'City': 'Kochi'}])
    d3 = pd.DataFrame({'Temp': pd.Series([-4, 31], index=['Leh', 'Kochi']),
                       'Rain': pd.Series([10], index=['Kochi'])})
    print(d1)
    print(d2)
    print(d3)
    Output
        City  Temp
    0    Leh    -4
    1  Kochi    31
        City  Temp
    0    Leh  -4.0
    1  Kochi   NaN
           Temp  Rain
    Kochi    31  10.0
    Leh      -4   NaN
  3. 3 marks

    Consider df = pd.read_csv('marks.csv', index_col='RollNo') where the file holds columns RollNo, Name, Maths, Science and English for five students. Write statements to:

    1. display the names of students with Maths above 80,
    2. add a column Total,
    3. display rows of roll numbers 2 to 4 with only Name and Total,
    4. delete the column English.
    Answer

    loc with a Boolean condition selects the names; the new column is a sum of three columns; a loc slice on roll numbers includes roll 4; drop(columns=...) removes English.

    Python
    import pandas as pd
    df = pd.read_csv('marks.csv', index_col='RollNo')
    print(df.loc[df['Maths'] > 80, 'Name'])
    df['Total'] = df['Maths'] + df['Science'] + df['English']
    print(df.loc[2:4, ['Name', 'Total']])
    df = df.drop(columns='English')
    print(df.columns.tolist())
    Output
    RollNo
    2     Diya
    4    Meera
    Name: Name, dtype: object
             Name  Total
    RollNo              
    2        Diya    274
    3       Kabir    195
    4       Meera    252
    ['Name', 'Maths', 'Science', 'Total']
  4. 3 marks

    What is index alignment? Explain with an example how + and add() with fill_value behave differently for two Series of runs scored by players in two matches.

    Answer

    When two Series are combined, Pandas pairs values that have the same index label, whatever their positions. This is called alignment. With +, a player who batted in only one match has nothing to pair with, so the result is NaN. add(other, fill_value=0) treats the missing score as 0, so the total equals the one score.

    Python
    import pandas as pd
    m1 = pd.Series([56, 12, 80], index=['Gill', 'Iyer', 'Pant'])
    m2 = pd.Series([34, 101], index=['Pant', 'Rahul'])
    print(m1 + m2)
    print(m1.add(m2, fill_value=0))
    Output
    Gill       NaN
    Iyer       NaN
    Pant     114.0
    Rahul      NaN
    dtype: float64
    Gill      56.0
    Iyer      12.0
    Pant     114.0
    Rahul    101.0
    dtype: float64
  5. 5 marks

    Explain read_csv() and to_csv() with the parameters sep, header, names, index_col and index. Write code that reads a semicolon-separated file stations.txt (no heading row; columns: code, station, state, platforms), keeps stations with more than 15 platforms and saves them to big.csv without row labels.

    Answer
    • sep: separator character (',' by default).
    • header: row number holding the labels in read_csv(); None if there is none. In to_csv(), header=False skips writing column labels.
    • names: list of column labels to use while reading.
    • index_col: column to use as row labels while reading.
    • index: in to_csv(), whether to write row labels (True by default).
    Python
    import pandas as pd
    st = pd.read_csv('stations.txt', sep=';', header=None,
                     names=['Code', 'Station', 'State', 'Platforms'])
    big = st[st['Platforms'] > 15]
    big.to_csv('big.csv', index=False)
    print(open('big.csv').read())
    Output
    Code,Station,State,Platforms
    NDLS,New Delhi,Delhi,16
    MAS,Chennai Central,Tamil Nadu,17
    HWH,Howrah,West Bengal,23
  6. 3 marks

    Write a program that creates a DataFrame of three trains (TrainNo, Name, Coaches) and then: adds a new train using loc, renames the column Coaches to Cars, removes the train at row label 1 and prints each remaining train with iterrows().

    Answer

    loc[len(tr)] adds a row at the end; rename() and drop() return new DataFrames, so their results are assigned back; row label 1 (Mumbai Rajdhani) is dropped and the labels 0, 2, 3 remain.

    Python
    import pandas as pd
    tr = pd.DataFrame({'TrainNo': [12002, 12951, 22436],
                       'Name': ['Shatabdi', 'Rajdhani', 'Vande Bharat'],
                       'Coaches': [18, 22, 16]})
    tr.loc[len(tr)] = [12627, 'Karnataka Exp', 24]
    tr = tr.rename(columns={'Coaches': 'Cars'})
    tr = tr.drop(1)
    for label, row in tr.iterrows():
        print(label, row['TrainNo'], row['Name'], row['Cars'])
    Output
    0 12002 Shatabdi 18
    2 22436 Vande Bharat 16
    3 12627 Karnataka Exp 24
  7. 3 marks

    Write the output of the following code and explain each line.

    s = pd.Series([2, 4, 6, 8, 10], index=[10, 20, 30, 40, 50])
    print(s[20])
    print(s.iloc[2])
    print(s.loc[20:40].sum())
    print(s.iloc[1:3].tolist())

    Answer

    The index is made of integers, so s[20] is a label lookup and gives 4. s.iloc[2] is the third element, 6. s.loc[20:40] includes labels 20, 30 and 40, whose sum is 4 + 6 + 8 = 18. s.iloc[1:3] takes positions 1 and 2.

    Text
    4
    6
    18
    [4, 6]

Case-based

Case study 1

Ms. Kavita teaches Class XII at a school in Lucknow. She stores the pre-board results in a DataFrame res:

res = pd.DataFrame({'Name': ['Ishaan', 'Tanvi', 'Farhan', 'Sneha'], 'Section': ['A', 'B', 'A', 'C'], 'Percent': [86.4, 92.0, 74.5, 81.2]}, index=['A101', 'A102', 'A103', 'A104'])

  1. 1 mark

    Write the statement to display the row labels and the column labels.

    Answer

    print(res.index) and print(res.columns)

  2. 1 mark

    What will print(res.shape) and print(res.size) display?

    Answer

    4 rows and 3 columns; 4 × 3 = 12 cells.

    Text
    (4, 3)
    12
  3. 2 marks

    Write a statement to display the names of Section A students who scored above 80%.

    Answer

    print(res.loc[(res['Section'] == 'A') & (res['Percent'] > 80), 'Name'])

  4. 1 mark

    Write the output of print(res.dtypes).

    Answer

    Text columns are object in Pandas 1.x/2.x (Pandas 3 prints str); Percent holds decimals, so it is float64.

    Text
    Name        object
    Section     object
    Percent    float64
    dtype: object
Case study 2

A stationery shop in Pune records the quantity sold this week in two Series:

wk1 = pd.Series([40, 25, 60, 10], index=['Pen', 'Pencil', 'Notebook', 'Geometry box'])

wk2 = pd.Series([55, 30, 12], index=['Notebook', 'Pen', 'Eraser'])

  1. 2 marks

    Write the output of print(wk1 + wk2).

    Answer

    Only Pen and Notebook are in both; the others give NaN, and the labels come out sorted.

    Text
    Eraser            NaN
    Geometry box      NaN
    Notebook        115.0
    Pen              70.0
    Pencil            NaN
    dtype: float64
  2. 1 mark

    Write a statement to get the total sales of both weeks without any NaN.

    Answer

    total = wk1.add(wk2, fill_value=0)

  3. 1 mark

    Write a statement to display the items of week 1 that sold fewer than 30 units.

    Answer

    print(wk1[wk1 < 30])

  4. 1 mark

    Write the output of print(wk1['Pencil':'Geometry box']).

    Answer

    A label slice includes both end labels.

    Text
    Pencil          25
    Notebook        60
    Geometry box    10
    dtype: int64
Case study 3

The sports teacher has a file entries.csv of students registered for Sports Day. Each row has Name, Class and Event. Some students were registered twice by mistake. She writes:

ent = pd.read_csv('entries.csv')

  1. 1 mark

    Write a statement to display only the rows that are exact repeats of an earlier row.

    Answer

    print(ent[ent.duplicated()])

  2. 1 mark

    Write a statement to remove the repeated rows and keep the first copy of each, changing ent itself.

    Answer

    ent = ent.drop_duplicates() (older form: ent.drop_duplicates(inplace=True))

  3. 1 mark

    Write a statement to rename the column Event to Activity.

    Answer

    ent = ent.rename(columns={'Event': 'Activity'})

  4. 1 mark

    Write a statement to save the cleaned data to final.csv with a semicolon separator and without row labels.

    Answer

    ent.to_csv('final.csv', sep=';', index=False)

Practicals

1Series from a dictionary and an ndarray

Create a Series capital from a dictionary of four states and their capitals, and a Series area from a NumPy array of the same states' areas (in thousand sq km) with the state names as index. Print both Series, the dtype of each and the area of the second state by position.

Solution
Python
import pandas as pd
import numpy as np
capital = pd.Series({'Rajasthan': 'Jaipur', 'Odisha': 'Bhubaneswar',
                     'Kerala': 'Thiruvananthapuram', 'Manipur': 'Imphal'})
area = pd.Series(np.array([342, 156, 39, 22]), index=capital.index)
print(capital)
print(area)
print(capital.dtype, area.dtype)
print('Second state:', area.index[1], area.iloc[1])
Output
Rajasthan                Jaipur
Odisha              Bhubaneswar
Kerala       Thiruvananthapuram
Manipur                  Imphal
dtype: object
Rajasthan    342
Odisha       156
Kerala        39
Manipur       22
dtype: int64
object int64
Second state: Odisha 156
Viva questions (3)
  • What becomes the index when a Series is created from a dictionary? The dictionary keys.
  • Why might area.dtype show int32 on some school computers? NumPy before 2.0 uses 32-bit integers on Windows.
  • How do you read the second element by position in every Pandas version? With s.iloc[1].

2Series from a scalar; Series attributes

Create a Series bus_fee of 1800 for each of the months Apr to Aug. Set its name to 'BusFee' and its index name to 'Month'. Print the Series and the attributes index, values, shape, size, ndim, empty and name.

Solution
Python
import pandas as pd
bus_fee = pd.Series(1800, index=['Apr', 'May', 'Jun', 'Jul', 'Aug'])
bus_fee.name = 'BusFee'
bus_fee.index.name = 'Month'
print(bus_fee)
print(bus_fee.index)
print(bus_fee.values)
print(bus_fee.shape, bus_fee.size, bus_fee.ndim, bus_fee.empty, bus_fee.name)
Output
Month
Apr    1800
May    1800
Jun    1800
Jul    1800
Aug    1800
Name: BusFee, dtype: int64
Index(['Apr', 'May', 'Jun', 'Jul', 'Aug'], dtype='object', name='Month')
[1800 1800 1800 1800 1800]
(5,) 5 1 False BusFee
Viva questions (3)
  • Is index compulsory when creating a Series from a scalar? Yes, to get more than one element.
  • What is the difference between size and count()? size includes NaN; count() does not.
  • Why is shape written as (5,)? It is a one-item tuple for a one-dimensional object.

3Mathematical operations on Series

Two Series hold the runs scored by players in the first and second innings of a Test match. Some players batted in only one innings. Print the total runs per player with + and with add() using fill_value=0, the difference between innings, and the players whose total is at least 100.

Solution
Python
import pandas as pd
inn1 = pd.Series([88, 12, 45, 131], index=['Jaiswal', 'Gill', 'Rahul', 'Pant'])
inn2 = pd.Series([54, 102, 9], index=['Jaiswal', 'Gill', 'Jadeja'])
print(inn1 + inn2)
total = inn1.add(inn2, fill_value=0)
print(total)
print(inn1.sub(inn2, fill_value=0))
print(total[total >= 100])
Output
Gill       114.0
Jadeja       NaN
Jaiswal    142.0
Pant         NaN
Rahul        NaN
dtype: float64
Gill       114.0
Jadeja       9.0
Jaiswal    142.0
Pant       131.0
Rahul       45.0
dtype: float64
Gill       -90.0
Jadeja      -9.0
Jaiswal     34.0
Pant       131.0
Rahul       45.0
dtype: float64
Gill       114.0
Jaiswal    142.0
Pant       131.0
dtype: float64
Viva questions (3)
  • Why does inn1 + inn2 show NaN for Rahul? Rahul is not a label in inn2, so there is nothing to add to.
  • Why is the result float64? NaN is a float, so the whole Series becomes float.
  • Name the method used for multiplication with a fill value. mul().

4Elements above the 75th percentile

Given a Series of the marks of 10 students, print all the elements that are above the 75th percentile. (CBSE suggested practical 2.)

Solution
Python
import pandas as pd
marks = pd.Series([56, 78, 91, 45, 67, 88, 72, 95, 60, 83],
                  index=['Asha', 'Bilal', 'Charu', 'Dhruv', 'Ekta',
                         'Faiz', 'Gauri', 'Harsh', 'Indu', 'Jatin'])
q3 = marks.quantile(0.75)
print('75th percentile:', q3)
print(marks[marks > q3])
Output
75th percentile: 86.75
Charu    91
Faiz     88
Harsh    95
dtype: int64
Viva questions (3)
  • What does quantile(0.75) return? The value below which 75% of the data lies.
  • Is the 75th percentile always one of the values? No; Pandas interpolates between two values by default.
  • Which indexing technique selects the elements? Boolean indexing.

5Exam result DataFrame: labels, data types and dimensions

Create a DataFrame for the examination result of six students (admission number as row label; Name, Class and Percent as columns). Display the row labels, column labels, data types of each column and the dimensions. (CBSE suggested practical 4.)

Solution
Python
import pandas as pd
result = pd.DataFrame({'Name': ['Ishaan', 'Tanvi', 'Farhan', 'Sneha', 'Yash', 'Pooja'],
                       'Class': ['12A', '12B', '12A', '12C', '12B', '12A'],
                       'Percent': [86.4, 92.0, 74.5, 81.2, 68.9, 95.6]},
                      index=['A101', 'A102', 'A103', 'A104', 'A105', 'A106'])
print(result)
print('Row labels:', result.index.tolist())
print('Column labels:', result.columns.tolist())
print(result.dtypes)
print('Dimensions:', result.ndim, 'Shape:', result.shape, 'Size:', result.size)
Output
        Name Class  Percent
A101  Ishaan   12A     86.4
A102   Tanvi   12B     92.0
A103  Farhan   12A     74.5
A104   Sneha   12C     81.2
A105    Yash   12B     68.9
A106   Pooja   12A     95.6
Row labels: ['A101', 'A102', 'A103', 'A104', 'A105', 'A106']
Column labels: ['Name', 'Class', 'Percent']
Name        object
Class       object
Percent    float64
dtype: object
Dimensions: 2 Shape: (6, 3) Size: 18
Viva questions (3)
  • What does dtypes return? A Series with the data type of each column.
  • What is the difference between shape and size? shape is (rows, columns); size is rows × columns.
  • Which attribute gives the transpose? T.

6Filtering rows: conditions and duplicate rows

A DataFrame holds library issue records (RollNo, Name, Book). Some records were entered twice. Display (a) the duplicate rows, (b) the records without duplicates, (c) the issues of the book 'Wings of Fire', and (d) students who borrowed more than one different book. (CBSE suggested practical 5.)

Solution
Python
import pandas as pd
lib = pd.DataFrame({'RollNo': [11, 14, 11, 20, 14, 11],
                    'Name': ['Nisha', 'Aman', 'Nisha', 'Ritu', 'Aman', 'Nisha'],
                    'Book': ['Wings of Fire', 'Godan', 'Wings of Fire',
                             'Malgudi Days', 'Wings of Fire', 'Gitanjali']})
print(lib[lib.duplicated()])
clean = lib.drop_duplicates()
print(clean)
print(clean[clean['Book'] == 'Wings of Fire'])
print(clean[clean.duplicated(subset=['RollNo'])]['Name'].unique())
Output
   RollNo   Name           Book
2      11  Nisha  Wings of Fire
   RollNo   Name           Book
0      11  Nisha  Wings of Fire
1      14   Aman          Godan
3      20   Ritu   Malgudi Days
4      14   Aman  Wings of Fire
5      11  Nisha      Gitanjali
   RollNo   Name           Book
0      11  Nisha  Wings of Fire
4      14   Aman  Wings of Fire
['Aman' 'Nisha']
Viva questions (3)
  • What does duplicated() return? A Boolean Series that is True for a row equal to an earlier row.
  • How do you keep the last copy instead of the first? Pass keep='last'.
  • What does subset do? It compares only the listed columns.

7Import and export between CSV and DataFrame

Read marks.csv (RollNo, Name, Maths, Science, English) with RollNo as the row index. Add a column Percent (average of the three subjects, rounded to 1 decimal place). Export students with Percent of at least 75 to merit.csv with a semicolon separator, and read the file back to check it. (CBSE suggested practical 6.)

Solution
Python
import pandas as pd
df = pd.read_csv('marks.csv', index_col='RollNo')
df['Percent'] = ((df['Maths'] + df['Science'] + df['English']) / 3).round(1)
merit = df[df['Percent'] >= 75]
merit.to_csv('merit.csv', sep=';')
print(open('merit.csv').read())
back = pd.read_csv('merit.csv', sep=';', index_col='RollNo')
print(back)
Output
RollNo;Name;Maths;Science;English;Percent
1;Aarav;78;82;74;78.0
2;Diya;91;88;95;91.3
4;Meera;88;79;85;84.0

         Name  Maths  Science  English  Percent
RollNo                                         
1       Aarav     78       82       74     78.0
2        Diya     91       88       95     91.3
4       Meera     88       79       85     84.0
Viva questions (3)
  • Which parameter of to_csv() changes the separator? sep.
  • What happens to the row labels in the file by default? They are written as the first column.
  • Why do we pass sep=';' again while reading? read_csv() assumes commas unless told otherwise.

8Add, delete and rename rows and columns

Create a DataFrame of four Indian states with columns Capital and Districts. Then: add a column Zone, add a new state row using loc, rename the column Districts to NumDistricts, delete the column Zone with del, and delete the row of one state with drop(). Print the DataFrame after each step.

Solution
Python
import pandas as pd
st = pd.DataFrame({'Capital': ['Jaipur', 'Patna', 'Shimla', 'Panaji'],
                   'Districts': [50, 38, 12, 2]},
                  index=['Rajasthan', 'Bihar', 'Himachal', 'Goa'])
st['Zone'] = ['North', 'East', 'North', 'West']
print(st)
st.loc['Tripura'] = ['Agartala', 8, 'North-East']
print(st)
st = st.rename(columns={'Districts': 'NumDistricts'})
del st['Zone']
st = st.drop('Bihar')
print(st)
Output
          Capital  Districts   Zone
Rajasthan  Jaipur         50  North
Bihar       Patna         38   East
Himachal   Shimla         12  North
Goa        Panaji          2   West
            Capital  Districts        Zone
Rajasthan    Jaipur         50       North
Bihar         Patna         38        East
Himachal     Shimla         12       North
Goa          Panaji          2        West
Tripura    Agartala          8  North-East
            Capital  NumDistricts
Rajasthan    Jaipur            50
Himachal     Shimla            12
Goa          Panaji             2
Tripura    Agartala             8
Viva questions (3)
  • What does axis=1 mean in drop()? Columns.
  • What does inplace=True do in older code? It changes the original DataFrame and returns None (deprecated from Pandas 3.1).
  • What happens if loc is given a row label that already exists? That row is overwritten.

9Iterating over a DataFrame

For a DataFrame of five students with marks in Maths, Science and English, use iterrows() to print a one-line report for each student with the total and a 'PASS'/'FAIL' remark (fail if any subject is below 33), and use items() to print the highest mark in each subject.

Solution
Python
import pandas as pd
df = pd.DataFrame({'Maths': [78, 30, 65, 88, 55],
                   'Science': [82, 62, 28, 79, 62],
                   'English': [74, 70, 60, 85, 58]},
                  index=['Aarav', 'Diya', 'Kabir', 'Meera', 'Rohan'])
for name, row in df.iterrows():
    remark = 'PASS' if row.min() >= 33 else 'FAIL'
    print(f'{name:<6} Total={row.sum():>3} {remark}')
for subject, col in df.items():
    print(subject, 'highest:', col.max())
Output
Aarav  Total=234 PASS
Diya   Total=162 FAIL
Kabir  Total=153 FAIL
Meera  Total=252 PASS
Rohan  Total=175 PASS
Maths highest: 88
Science highest: 82
English highest: 85
Viva questions (3)
  • What does each step of iterrows() give? A (row label, row Series) pair.
  • Which method replaced iteritems()? items().
  • Why should loops be avoided for calculations on large data? Column-wise operations are much faster.

10Selecting data with loc, iloc and slices

Create a DataFrame of trains (TrainNo as index; Name, From, To, Departure) and write statements to display: (a) the details of train 12951, (b) the Name and Departure of the first three trains using iloc, (c) trains from 12301 to 12951 in index order using loc, (d) the departure time of the last train.

Solution
Python
import pandas as pd
tr = pd.DataFrame({'Name': ['Howrah Rajdhani', 'Mumbai Rajdhani', 'Shatabdi', 'Vande Bharat'],
                   'From': ['New Delhi', 'New Delhi', 'Chennai', 'Varanasi'],
                   'To': ['Howrah', 'Mumbai', 'Mysuru', 'New Delhi'],
                   'Departure': ['16:50', '16:55', '06:00', '15:00']},
                  index=[12301, 12951, 12007, 22435])
tr.index.name = 'TrainNo'
print(tr.loc[12951])
print(tr.iloc[0:3, [0, 3]])
print(tr.loc[12301:12951])
print(tr.iloc[-1]['Departure'])
Output
Name         Mumbai Rajdhani
From               New Delhi
To                    Mumbai
Departure              16:55
Name: 12951, dtype: object
                    Name Departure
TrainNo                           
12301    Howrah Rajdhani     16:50
12951    Mumbai Rajdhani     16:55
12007           Shatabdi     06:00
                    Name       From      To Departure
TrainNo                                              
12301    Howrah Rajdhani  New Delhi  Howrah     16:50
12951    Mumbai Rajdhani  New Delhi  Mumbai     16:55
15:00
Viva questions (3)
  • Does a loc slice include the end label? Yes.
  • Does loc[12301:12951] sort the index first? No, it follows the current order of rows.
  • How do you select every row but only one column with loc? df.loc[:, 'col'].

Common mistakes

  • Writing pd.series(...) or pd.dataframe(...). Python is case-sensitive: the correct names are pd.Series and pd.DataFrame.

  • Creating a Series from a scalar without an index, expecting many elements. pd.Series(5) has one element; write pd.Series(5, index=[...]).

  • Expecting a label slice to stop before the end label. s['b':'d'] includes 'd'; only position slices such as s[1:3] exclude the end.

  • Using s[0] to get the first element of a Series whose labels are words or roll numbers. It raises KeyError in Pandas 3 (and a warning in 2.x). Use s.iloc[0].

  • Adding two Series and expecting position-wise results. Pandas matches labels, so unmatched labels give NaN; use add(other, fill_value=0) when a missing value should count as 0.

  • Writing df[df.Maths > 80 and df.Science > 80]. Use & instead of and, and put brackets around each condition: df[(df.Maths > 80) & (df.Science > 80)].

  • Calling df.drop('Grade') to remove a column. Without axis=1 (or columns=), Pandas looks for a row called Grade and raises KeyError.

  • Forgetting that drop() and rename() return a new DataFrame. Assign the result back (df = df.drop(...)). Never combine the two styles: df = df.drop(..., inplace=True) sets df to None.

  • Using DataFrame.append() or iteritems() from old books. Both were removed in Pandas 2.0; use pd.concat() / df.loc[new_label] = ... and items().

  • Exporting with to_csv('f.csv') and then finding an extra unnamed column when reading back. Pass index=False when the row labels are just 0, 1, 2, ...