Data Handling using Pandas - I
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()anditems(). - Students will be able to import and export data with
read_csv()andto_csv()usingsep,header,index,namesandindex_col.
Lesson plan
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.
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.
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.
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.
Students filter a Series of daily temperatures for hot days (> 40), then combine conditions with & and |. Short quiz on head() and tail() defaults.
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).
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.
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.
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.
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.
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.
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.
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).
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.
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']}))[72 85 64] Ravi 72 Sana 85 Tara 64 dtype: int64 Marks House 0 72 Red 1 85 Blue 2 64 Red
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.
| Feature | Series | DataFrame | NumPy ndarray |
|---|---|---|---|
| Dimensions | 1 (one column) | 2 (rows and columns) | 1 or more |
| Labels | Row index | Row index and column labels | Only integer positions |
| Data types | One type for all values | Each column can have its own type | One type for all values |
| Size | Values can change; size can grow | Values and size can change | Fixed size once created |
| Missing data | Shown as NaN | Shown as NaN | No built-in notion |
| Arithmetic on two objects | Matched by index label | Matched by row and column labels | Matched 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 aValueError. - 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.
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)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
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.
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)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)
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.
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)Kerala Thiruvananthapuram Punjab Chandigarh Assam Dispur dtype: object Japan 45.0 India 6.0 Nepal NaN dtype: float64
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.
| Attribute | Gives | Example result |
|---|---|---|
s.index | The row labels | Index(['Mon', 'Tue', ...], dtype='object') |
s.values | The data as a NumPy array | [41 39 44 40] |
s.dtype | Data type of the values | int64 |
s.shape | Tuple with the number of elements | (4,) |
s.size | Number of elements (NaN included) | 4 |
s.ndim | Number of dimensions (always 1) | 1 |
s.name | Name of the Series | 'MaxTemp' |
s.index.name | Name of the index | 'Day' |
s.empty | True if there are no elements | False |
s.hasnans | True if any value is NaN | False |
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)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
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']ors.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.
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]])603 722 2955 1092 Agra 603 Pune 722 dtype: int64 Pune 722 Bhopal 1092 dtype: int64
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]ors.iloc[start:stop]work like list slices: the stop position is excluded. - Label slices
s['b':'d']ors.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.
| Expression | Kind | Elements returned |
|---|---|---|
s[1:3] | position | b, c (position 3 excluded) |
s.iloc[1:3] | position | b, c |
s['b':'d'] | label | b, c, d (end label included) |
s.loc['b':'d'] | label | b, c, d |
s[-2:] | position | d, e |
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())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]
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.
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())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, unlikesize, which counts all.
These are handy for a quick look at long data, such as 300 students' marks.
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())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
float64if 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.
| Operator | Method | With fill_value |
|---|---|---|
s1 + s2 | s1.add(s2) | s1.add(s2, fill_value=0) |
s1 - s2 | s1.sub(s2) | s1.sub(s2, fill_value=0) |
s1 * s2 | s1.mul(s2) | s1.mul(s2, fill_value=1) |
s1 / s2 | s1.div(s2) | s1.div(s2, fill_value=1) |
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'])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
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.
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)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.
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)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().
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)Maths Hindi Aarav 78.0 NaN Diya 91.0 88.0 Kabir 65.0 72.0 Meera NaN 69.0
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".
import pandas as pd
df = pd.read_csv('marks.csv')
print(df.head(3))
print()
print(df.tail(2))
print()
print(df.head(-3))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.
| Attribute | Gives |
|---|---|
df.index | Row labels |
df.columns | Column labels |
df.dtypes | Data type of each column (a Series) |
df.values | All data as a 2-D NumPy array |
df.shape | Tuple (number of rows, number of columns) |
df.size | Total number of cells = rows × columns |
df.ndim | Number of dimensions (2) |
df.T | Transpose: rows become columns and columns become rows |
df.empty | True if the DataFrame has no cells |
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)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.5df.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.Mathsdoes the same, but only when the label is a valid Python name (no spaces, not a method name likecount).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.
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])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 70Slicing 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.
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]) 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 95Here 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.
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'])]) 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 ChessAdding 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.
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)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
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=1means columns.- The clearer keywords
df.drop(index=[...])anddf.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.
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) 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 62The 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 = [...].
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())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), whererowis 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.
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())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; usesep=';'orsep='\t'for other files.header: the row number holding column labels (0by default). Useheader=Nonewhen the file has no heading row; columns are then numbered 0, 1, 2, ...names: a list of column labels to use (usually withheader=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).
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) 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 23Show 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;Falseleaves them out.index:True(default) writes the row labels as the first column;Falseleaves them out. Forgettingindex=Falseis 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.
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))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.
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))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=0means rows (index),axis=1means 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
sepinread_csv()andto_csv().
MCQs
Tap an option to check it, or use Show answers above to reveal them all.
-
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. -
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.
-
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. -
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.
-
s = pd.Series({'a': 1, 'b': 2}, index=['b', 'c']). What are the values ofs?Answer(b)
[2, nan]Only the labels in
indexare used. 'b' gets 2 and 'c' is not a key, so it gets NaN. -
s = pd.Series([3, 6, 9, 12]). What is printed byprint(list(s.index))?Answer(c)
[0, 1, 2, 3]With no index given, Pandas numbers the elements from 0.
-
s = pd.Series([5, 10, 15, 20, 25], index=list('pqrst')). How many elements doess['q':'s']return?Answer(b) 3
A label slice includes both ends: q, r and s.
-
For the same Series
s(labels p to t), which labels doess[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.
-
Which Series attribute gives the total number of elements, including NaN values?
Answer(d)
sizesizecounts every element. The methodcount()skips NaN;ndimis always 1 for a Series;shapehas only one item. -
What is printed by
print(pd.Series(dtype='float64').empty)?Answer(a) True
The Series has no elements, so
emptyis True. -
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. -
s1 = pd.Series([1, 2, 3], index=['x', 'y', 'z'])ands2 = 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.
-
s = pd.Series([3, 6, 9, 12, 15, 18]). What is printed byprint(s.tail(2).tolist())?Answer(b)
[15, 18]tail(2)returns the last two elements. -
How many rows does
df.head()return for a DataFrame with 50 rows?Answer(d) 5
The default value of n in
head()andtail()is 5. -
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]ilocalways uses positions.s[0]treats 0 as a label in Pandas 3 and raises KeyError;s.loc[0]always looks for label 0. -
s = pd.Series([12, 45, 7, 60, 33]). What is printed byprint(s[s > 30].tolist())?Answer(b)
[45, 60, 33]Boolean indexing keeps the values greater than 30, in their original order.
-
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.
-
A DataFrame
dfhas 4 rows and 3 columns. What is printed byprint(df.shape, df.size)?Answer(d) (4, 3) 12
shapeis (rows, columns) andsizeis rows × columns = 12. -
Which statement permanently removes the column 'Fees' from
df?Answer(c)
df = df.drop('Fees', axis=1)axis=1points to columns, and assigning the result back makes the change permanent. The older formdf.drop('Fees', axis=1, inplace=True)does the same. The first two options look for a row called Fees; there is noremove()method. -
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. -
What does
df.Treturn?Answer(a) The transpose of
dfTswaps rows and columns. -
Which
read_csv()parameter makes a column of the file the row labels of the DataFrame?Answer(c)
index_colindex_col='RollNo'(or a column number) uses that column as the index. -
Which
to_csv()call writes the file without row labels?Answer(b)
df.to_csv('a.csv', index=False)index=Falseleaves out the row labels;header=Falseleaves out the column labels. -
dfhas row labels 'R1' to 'R6'. How many rows doesdf.loc['R2':'R4']return?Answer(d) 3
locslices include the end label: R2, R3 and R4. -
Which statement renames the column 'Amt' to 'Amount' and leaves other columns unchanged?
Answer(a)
df.rename(columns={'Amt': 'Amount'})The
columnsdictionary maps old label to new label.index=renames rows; assigningdf.columnsmust list every column.
Assertion–Reason
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.
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.
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.
Assertion (A):
df.drop('Marks')removes the column Marks fromdf.Reason (R): By default
drop()works alongaxis=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.
Assertion (A):
df.sizeof a DataFrame with 30 rows and 5 columns is 150.Reason (R):
sizegives the number of rows of a DataFrame.Answer(C)
A is true (30 × 5). R is false:
sizecounts all cells;len(df)ordf.shape[0]gives the number of rows.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
- 2 marks
Give two differences between a Pandas Series and a NumPy ndarray.
Answer- A Series has an index of labels (which can be text), while an ndarray is accessed only by integer positions.
- 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 marks
Write Python code to create a Series
feesholding 2500 for each of the months 'Apr', 'May' and 'Jun'.AnswerA scalar is repeated for every label in the index.
Python import pandas as pd fees = pd.Series(2500, index=['Apr', 'May', 'Jun']) print(fees)OutputApr 2500 May 2500 Jun 2500 dtype: int64
- 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.AnswerThe 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)OutputIndex(['Mumbai', 'Delhi', 'Kolkata'], dtype='object') [12.4 16.8 4.5]
- 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'])AnswerBoth 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 - 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])AnswerArithmetic 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 - 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)AnswerOnly 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 - 2 marks
For the Series
aandbof the previous question, write the output ofprint(a.add(b, fill_value=0)).AnswerThe 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 - 2 marks
Differentiate between
locandilocwith one example each.Answerlocselects by label, and alocslice includes the end label:df.loc['S1':'S3', 'Maths']gives rows S1, S2 and S3.ilocselects by integer position, and anilocslice excludes the end position:df.iloc[0:2, 1]gives the first two rows of the second column. - 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))Answerhead(2)gives the first two elements. A negative n intail(-4)means all elements except the first 4.Text A 90 B 75 dtype: int64 E 95 F 71 dtype: int64 - 2 marks
Name any four attributes of a DataFrame and state what each returns.
Answerindex: row labelscolumns: column labelsdtypes: data type of each columnshape: (rows, columns) as a tuple
(Others:
values,size,T,empty,ndim.) - 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])AnswerSelecting 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 - 2 marks
What is the difference between
del df['Bonus']anddf.drop('Bonus', axis=1)?Answerdel df['Bonus']deletes the column fromdfitself and returns nothing; it works on one column only.df.drop('Bonus', axis=1)returns a new DataFrame without the column and leavesdfunchanged unless the result is assigned back (orinplace=Trueis passed in older code). It can also remove several columns or rows at once. - 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.AnswerUse
header=Nonebecause the file has no heading row, andnamesto supply labels. For a file containing lines such asS01|Geometry box|40:Python import pandas as pd df = pd.read_csv('stock.csv', sep='|', header=None, names=['Code', 'Item', 'Qty']) print(df)OutputCode Item Qty 0 S01 Geometry box 40 1 S02 Drawing sheet 250 2 S03 Glue stick 75
- 2 marks
Why does an extra column such as
Unnamed: 0often appear when a DataFrame saved withto_csv()is read back? How do you prevent it?AnswerBy 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, namedUnnamed: 0.Prevent it by writing with
df.to_csv('f.csv', index=False), or read it back withindex_col=0. - 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)AnswerColumn 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
- 3 marks
Create a Series
marksfrom the NumPy array[67, 88, 94, 72, 55]with index['Riya', 'Om', 'Liza', 'Jai', 'Esha']. Then write statements to:- print the marks of Liza,
- print the first three students,
- print students who scored more than 70,
- add 5 grace marks to every student who scored below 60.
AnswerUse
locfor 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)Output94 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
- 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.
AnswerDictionary 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)OutputCity 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 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:- display the names of students with Maths above 80,
- add a column Total,
- display rows of roll numbers 2 to 4 with only Name and Total,
- delete the column English.
Answerlocwith a Boolean condition selects the names; the new column is a sum of three columns; alocslice 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())OutputRollNo 2 Diya 4 Meera Name: Name, dtype: object Name Total RollNo 2 Diya 274 3 Kabir 195 4 Meera 252 ['Name', 'Maths', 'Science', 'Total'] - 3 marks
What is index alignment? Explain with an example how
+andadd()withfill_valuebehave differently for two Series of runs scored by players in two matches.AnswerWhen 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))OutputGill 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 marks
Explain
read_csv()andto_csv()with the parameterssep,header,names,index_colandindex. Write code that reads a semicolon-separated filestations.txt(no heading row; columns: code, station, state, platforms), keeps stations with more than 15 platforms and saves them tobig.csvwithout row labels.Answersep: separator character (','by default).header: row number holding the labels inread_csv();Noneif there is none. Into_csv(),header=Falseskips writing column labels.names: list of column labels to use while reading.index_col: column to use as row labels while reading.index: into_csv(), whether to write row labels (Trueby 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())OutputCode,Station,State,Platforms NDLS,New Delhi,Delhi,16 MAS,Chennai Central,Tamil Nadu,17 HWH,Howrah,West Bengal,23
- 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 withiterrows().Answerloc[len(tr)]adds a row at the end;rename()anddrop()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'])Output0 12002 Shatabdi 18 2 22436 Vande Bharat 16 3 12627 Karnataka Exp 24
- 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())AnswerThe 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
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 mark
Write the statement to display the row labels and the column labels.
Answerprint(res.index)andprint(res.columns) - 1 mark
What will
print(res.shape)andprint(res.size)display?Answer4 rows and 3 columns; 4 × 3 = 12 cells.
Text (4, 3) 12 - 2 marks
Write a statement to display the names of Section A students who scored above 80%.
Answerprint(res.loc[(res['Section'] == 'A') & (res['Percent'] > 80), 'Name']) - 1 mark
Write the output of
print(res.dtypes).AnswerText columns are
objectin Pandas 1.x/2.x (Pandas 3 printsstr); Percent holds decimals, so it isfloat64.Text Name object Section object Percent float64 dtype: object
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'])
- 2 marks
Write the output of
print(wk1 + wk2).AnswerOnly 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 - 1 mark
Write a statement to get the total sales of both weeks without any NaN.
Answertotal = wk1.add(wk2, fill_value=0) - 1 mark
Write a statement to display the items of week 1 that sold fewer than 30 units.
Answerprint(wk1[wk1 < 30]) - 1 mark
Write the output of
print(wk1['Pencil':'Geometry box']).AnswerA label slice includes both end labels.
Text Pencil 25 Notebook 60 Geometry box 10 dtype: int64
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 mark
Write a statement to display only the rows that are exact repeats of an earlier row.
Answerprint(ent[ent.duplicated()]) - 1 mark
Write a statement to remove the repeated rows and keep the first copy of each, changing
entitself.Answerent = ent.drop_duplicates()(older form:ent.drop_duplicates(inplace=True)) - 1 mark
Write a statement to rename the column Event to Activity.
Answerent = ent.rename(columns={'Event': 'Activity'}) - 1 mark
Write a statement to save the cleaned data to
final.csvwith a semicolon separator and without row labels.Answerent.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.
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])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.dtypeshow 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.
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)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
indexcompulsory when creating a Series from a scalar? Yes, to get more than one element. - What is the difference between
sizeandcount()?sizeincludes NaN;count()does not. - Why is
shapewritten 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.
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])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 + inn2show NaN for Rahul? Rahul is not a label ininn2, 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.)
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])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.)
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)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
dtypesreturn? A Series with the data type of each column. - What is the difference between
shapeandsize?shapeis (rows, columns);sizeis 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.)
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())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
subsetdo? 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.)
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)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.0Viva 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.
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) 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 8Viva questions (3)
- What does
axis=1mean indrop()? Columns. - What does
inplace=Truedo in older code? It changes the original DataFrame and returns None (deprecated from Pandas 3.1). - What happens if
locis 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.
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())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.
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'])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:00Viva questions (3)
- Does a
locslice 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(...)orpd.dataframe(...). Python is case-sensitive: the correct names arepd.Seriesandpd.DataFrame.Creating a Series from a scalar without an index, expecting many elements.
pd.Series(5)has one element; writepd.Series(5, index=[...]).Expecting a label slice to stop before the end label.
s['b':'d']includes'd'; only position slices such ass[1:3]exclude the end.Using
s[0]to get the first element of a Series whose labels are words or roll numbers. It raisesKeyErrorin Pandas 3 (and a warning in 2.x). Uses.iloc[0].Adding two Series and expecting position-wise results. Pandas matches labels, so unmatched labels give
NaN; useadd(other, fill_value=0)when a missing value should count as 0.Writing
df[df.Maths > 80 and df.Science > 80]. Use&instead ofand, and put brackets around each condition:df[(df.Maths > 80) & (df.Science > 80)].Calling
df.drop('Grade')to remove a column. Withoutaxis=1(orcolumns=), Pandas looks for a row called Grade and raisesKeyError.Forgetting that
drop()andrename()return a new DataFrame. Assign the result back (df = df.drop(...)). Never combine the two styles:df = df.drop(..., inplace=True)setsdftoNone.Using
DataFrame.append()oriteritems()from old books. Both were removed in Pandas 2.0; usepd.concat()/df.loc[new_label] = ...anditems().Exporting with
to_csv('f.csv')and then finding an extra unnamed column when reading back. Passindex=Falsewhen the row labels are just 0, 1, 2, ...