Data Handling using Pandas - II
Overview
This chapter moves from storing data in DataFrames to summarising it. Students compute statistics such as mean, median, mode and quartiles, sort and group rows to answer questions like "total spend per category", handle missing values, and move data between Pandas and a MySQL database.
The CBSE 2025-26 Unit 1 topic list stops at Series/DataFrame operations and CSV import/export, but the learning outcomes include importing/exporting data between MySQL and Pandas, and the suggested practicals need quantile() (75th percentile), group by and duplicate-row filtering. Other statistics, describe(), sorting, missing values, re-indexing and pivoting are marked Enrichment; they are useful in practicals and projects.
Learning outcomes
- Students will be able to find the 75th percentile (and other quantiles) of a Series or column and filter the values above it.
- Students will be able to group rows with
groupby()and compute totals, averages and counts per group. - Students will be able to compute
max,min,count,sum,mean,median,mode,stdandvaron a Series or DataFrame, along rows or columns. - Students will be able to read the summary produced by
describe(). - Students will be able to sort a DataFrame by one or more columns with
sort_values()and by labels withsort_index(). - Students will be able to detect, drop and fill missing values with
isnull(),dropna()andfillna(). - Students will be able to import a MySQL table into a DataFrame with
read_sql_query()and export a DataFrame to MySQL withto_sql().
Lesson plan
Give the class a printout of 40 students' marks and ask: who is the topper, what is the class average, how many scored above 80? Note how slow it is by hand. Introduce descriptive statistics and the syllabus scope (in syllabus vs Enrichment).
On the board, work out mean, median and mode of seven marks by hand. In the lab, students check them with Pandas, then repeat on a DataFrame with axis=0 and axis=1. Discuss why mode() returns a Series and why numeric_only=True is needed when a text column is present.
Sort ten marks on the board, find the position (n - 1) × 0.75 and interpolate by hand. Students verify with quantile(0.75) and then do CBSE practical 2: print the elements above the 75th percentile.
Compare two batsmen with the same average but different consistency; the smaller standard deviation is the steadier player. Students run describe() and label each row of its output on a worksheet.
Students produce a rank list with sort_values(by='Total', ascending=False), break ties with a second column, and show the top three with head(3).
Act it out: students stand in groups by house colour and each group reports its total points. Then do CBSE practical 3 (quarterly sales: total expenditure per category) and extend to agg(['sum', 'mean', 'count']).
Give a marks sheet where absentees are blank. Students count NaN per column, compare dropna() with fillna(0) and with filling the column mean, and discuss which is fair for a result sheet.
Show set_index(), reset_index() after a filter, and a quick pivot_table() of sales by quarter and category.
Demonstrate pip install pymysql sqlalchemy, create a student table in MySQL, read it with read_sql_query(), add a computed column in Pandas and write it back with to_sql(if_exists='replace', index=False). Students check the new table at the mysql> prompt.
Quick quiz from the output questions in this chapter; students explain each answer to a partner. Assign the practicals for the practical file.
Notes
What this chapter adds: summarising data
In Pandas - I we stored data in Series and DataFrames. Now we summarise it: a few numbers or a short table that describe hundreds of rows. Most functions here work on both a Series and a DataFrame:
- On a Series they return a single value.
- On a DataFrame they work column by column by default (
axis=0) and return a Series with one value per column. Withaxis=1they work row by row (for example, each student's total).
Missing values (NaN) are skipped by these functions unless stated otherwise.
The examples below use this result DataFrame of six students:
import pandas as pd
df = pd.DataFrame({'Name': ['Aditi', 'Bharat', 'Chirag', 'Devika', 'Eshan', 'Fatima'],
'Maths': [88, 67, 92, 75, 59, 81],
'Science': [79, 72, 95, 81, 64, 81],
'English': [91, 58, 84, 77, 70, 86]})
print(df)Name Maths Science English 0 Aditi 88 79 91 1 Bharat 67 72 58 2 Chirag 92 95 84 3 Devika 75 81 77 4 Eshan 59 64 70 5 Fatima 81 81 86
Quantiles and the 75th percentile (Practical list)
A quantile splits sorted data at a fraction. s.quantile(q) returns the value below which a fraction q of the data lies:
q=0.25: first quartile (25th percentile)q=0.5: median (the default)q=0.75: third quartile (75th percentile)
How Pandas finds it (linear interpolation): sort the values, compute the position (n - 1) × q counting from 0, and if it falls between two values, take the matching fraction of the gap. For 10 marks and q=0.75, the position is 9 × 0.75 = 6.75, so the answer is the 7th value plus 0.75 of the gap to the 8th.
A list of fractions returns several quantiles at once. On a DataFrame, pass numeric_only=True when a text column is present.
CBSE practical: print the elements above the 75th percentile with s[s > s.quantile(0.75)].
import pandas as pd
marks = pd.Series([56, 78, 91, 45, 67, 88, 72, 95, 60, 83])
print(sorted(marks))
print(marks.quantile(0.75))
print(marks[marks > marks.quantile(0.75)])
print(marks.quantile([0.25, 0.5, 0.75]))[45, 56, 60, 67, 72, 78, 83, 88, 91, 95] 86.75 2 91 5 88 7 95 dtype: int64 0.25 61.75 0.50 75.00 0.75 86.75 dtype: float64
Check by hand: sorted, positions 6 and 7 (0-based) hold 83 and 88, so the 75th percentile is 83 + 0.75 × (88 − 83) = 86.75. Board questions sometimes say "quartile"; Q1, Q2 and Q3 are the 25th, 50th and 75th percentiles.
Quantiles of a DataFrame
df.quantile(q, numeric_only=True) gives the quantile of every numeric column. With a single q the result is a Series named after q; with a list of fractions the result is a DataFrame with one row per fraction.
Why numeric_only=True? Pandas 1.x quietly skipped text columns (with a warning in 1.5), but Pandas 2.0 and later raise TypeError if a text column is included. Passing the parameter makes the same code work in every version.
import pandas as pd
df = pd.DataFrame({'Name': ['Aditi', 'Bharat', 'Chirag', 'Devika', 'Eshan', 'Fatima'],
'Maths': [88, 67, 92, 75, 59, 81],
'Science': [79, 72, 95, 81, 64, 81]})
print(df.quantile(0.75, numeric_only=True))
print(df.quantile([0.25, 0.75], numeric_only=True))
print(df[df['Maths'] > df['Maths'].quantile(0.75)]['Name'].tolist())Maths 86.25
Science 81.00
Name: 0.75, dtype: float64
Maths Science
0.25 69.00 73.75
0.75 86.25 81.00
['Aditi', 'Chirag']Grouping rows with groupby() (Practical list)
df.groupby('col') splits the rows into groups that share the same value in col, applies a function to each group, and combines the answers into a new Series or DataFrame (split, apply, combine). The group values become the index of the result, sorted.
df.groupby('Category')['Expenditure'].sum(): total per category (a Series)..mean(),.max(),.min(),.count()work the same way;.size()counts rows in each group.df.groupby('Category').get_group('Lab'): the rows of one group.df.groupby(['Quarter', 'Category']): groups on two columns.
Always select the numeric column(s) before applying sum()/mean(), or pass numeric_only=True. In Pandas 2.0 and later, text columns otherwise cause errors (mean) or get joined end to end (sum); Pandas 1.x silently dropped them.
CBSE practical 3 uses a quarterly sales file sales.csv with columns Quarter, Category, Item and Expenditure.
import pandas as pd
sales = pd.read_csv('sales.csv')
print(sales)
print()
print(sales.groupby('Category')['Expenditure'].sum())
print()
print(sales.groupby('Category').size())
print()
print(sales.groupby('Category').get_group('Lab'))Quarter Category Item Expenditure 0 Q1 Stationery Registers 18500 1 Q1 Sports Cricket kits 42000 2 Q1 Lab Chemicals 26000 3 Q2 Stationery Chalk boxes 6500 4 Q2 Lab Glassware 31500 5 Q2 Sports Footballs 12000 6 Q3 Stationery Printer paper 22000 7 Q3 Sports Badminton sets 15500 8 Q4 Lab Microscopes 64000 9 Q4 Stationery Registers 17000 Category Lab 121500 Sports 69500 Stationery 64000 Name: Expenditure, dtype: int64 Category Lab 3 Sports 3 Stationery 4 dtype: int64 Quarter Category Item Expenditure 2 Q1 Lab Chemicals 26000 4 Q2 Lab Glassware 31500 8 Q4 Lab Microscopes 64000
Link it to SQL, which students meet in Unit 2: SELECT Category, SUM(Expenditure) FROM sales GROUP BY Category; does the same job as the first groupby line.
More with groupby(): several functions and two columns
.agg(['sum', 'mean', 'count'])applies several functions to each group, giving one column per function..agg({'Expenditure': 'sum', 'Item': 'count'})applies a different function to each column.- Grouping on two columns gives a result with a two-level index. Call
.reset_index()to turn the group labels back into ordinary columns, which is handy before exporting to CSV. sort_values()after agroupbyranks the groups.
import pandas as pd
sales = pd.read_csv('sales.csv')
print(sales.groupby('Category')['Expenditure'].agg(['sum', 'mean', 'count']))
print()
q = sales.groupby(['Quarter', 'Category'])['Expenditure'].sum()
print(q.head(4))
print()
print(q.reset_index().head(3)) sum mean count
Category
Lab 121500 40500.000000 3
Sports 69500 23166.666667 3
Stationery 64000 16000.000000 4
Quarter Category
Q1 Lab 26000
Sports 42000
Stationery 18500
Q2 Lab 31500
Name: Expenditure, dtype: int64
Quarter Category Expenditure
0 Q1 Lab 26000
1 Q1 Sports 42000
2 Q1 Stationery 18500Maximum, minimum, count and sum (Enrichment)
max()/min(): largest / smallest value. On text, the comparison is alphabetical.count(): number of non-NaN values.sum(): total. On a DataFrame with a text column,sum()joins the strings, so passnumeric_only=True(or select the numeric columns).idxmax()/idxmin(): the label where the maximum / minimum occurs, useful for "who topped?".
axis=1 applies the function across each row.
import pandas as pd
df = pd.DataFrame({'Maths': [88, 67, 92, 75, 59, 81],
'Science': [79, 72, 95, 81, 64, 81],
'English': [91, 58, 84, 77, 70, 86]},
index=['Aditi', 'Bharat', 'Chirag', 'Devika', 'Eshan', 'Fatima'])
print(df.max())
print(df.min(axis=1))
print(df.sum(axis=1).idxmax(), df['English'].idxmin())
print(df['Maths'].count(), df.count().sum())Maths 92 Science 95 English 91 dtype: int64 Aditi 79 Bharat 58 Chirag 84 Devika 75 Eshan 59 Fatima 81 dtype: int64 Chirag Bharat 6 18
Mean, median and mode (Enrichment)
mean(): arithmetic average.median(): middle value after sorting; for an even count, the average of the two middle values.mode(): the most frequent value(s). It always returns a Series (or a DataFrame for a DataFrame), because data can have more than one mode. Uses.mode()[0]for the first mode.
On a DataFrame with text columns, write df.mean(numeric_only=True) and df.median(numeric_only=True). Without it, Pandas 2.0 and later raise TypeError.
import pandas as pd
runs = pd.Series([45, 12, 78, 45, 102, 12, 33])
print(runs.mean(), runs.median())
print(runs.mode())
print(runs.mode()[0])
df = pd.DataFrame({'House': ['Red', 'Blue', 'Red', 'Green'], 'Points': [12, 9, 15, 9]})
print(df.mean(numeric_only=True))
print(df.mode())46.714285714285715 45.0 0 12 1 45 dtype: int64 12 Points 11.25 dtype: float64 House Points 0 Red 9
The output 0 12 / 1 45 shows two modes, labelled 0 and 1. Students often expect a single number; ask them why Pandas cannot give one here.
Variance and standard deviation (Enrichment)
Variance (var()) measures how far values spread from the mean: the average of squared differences from the mean. Standard deviation (std()) is its square root, in the same units as the data.
Pandas divides by n - 1 (the sample formula, ddof=1). NumPy's np.std() divides by n by default, so the two can differ slightly; pass ddof=0 to std() to get NumPy's value.
A small standard deviation means consistent values. Two batters can have the same average and very different consistency: below, both average 40, but batter B's scores swing far more.
import pandas as pd
batterA = pd.Series([40, 42, 38, 41, 39])
batterB = pd.Series([5, 90, 12, 85, 8])
print(batterA.mean(), batterB.mean())
print(batterA.var(), round(batterB.var(), 2))
print(round(batterA.std(), 2), round(batterB.std(), 2))
print(round(batterA.std(ddof=0), 2))40.0 40.0 2.5 1889.5 1.58 43.47 1.41
Summary in one call: describe() (Enrichment)
describe() returns count, mean, std, min, 25%, 50% (median), 75% and max for every numeric column. On a Series of text it gives count, unique, top (most frequent) and freq instead. describe(include='all') covers both kinds of column.
It is a quick first look at any new data file before deeper analysis.
import pandas as pd
df = pd.DataFrame({'Name': ['Aditi', 'Bharat', 'Chirag', 'Devika', 'Eshan', 'Fatima'],
'Maths': [88, 67, 92, 75, 59, 81],
'Science': [79, 72, 95, 81, 64, 81]})
print(df.describe())
print(df['Name'].describe())Maths Science count 6.000000 6.000000 mean 77.000000 78.666667 std 12.569805 10.366613 min 59.000000 64.000000 25% 69.000000 73.750000 50% 78.000000 80.000000 75% 86.250000 81.000000 max 92.000000 95.000000 count 6 unique 6 top Aditi freq 1 Name: Name, dtype: object
Statistics functions at a glance
All of these skip NaN by default. On a DataFrame they work column-wise unless axis=1 is given.
| Function | Returns | Note |
|---|---|---|
max(), min() | Largest / smallest value | idxmax()/idxmin() give the label |
count() | Number of non-NaN values | Compare: size counts NaN too |
sum() | Total | Use numeric_only=True with text columns |
mean() | Average | Use numeric_only=True with text columns |
median() | Middle value | Same as quantile(0.5) |
mode() | Most frequent value(s) | Always a Series/DataFrame; mode()[0] for one value |
quantile(q) | Value at fraction q | 0.25, 0.5, 0.75 are the quartiles |
var() | Variance | Divides by n - 1 |
std() | Standard deviation | Square root of variance |
describe() | Table of the above | Numeric columns by default |
Sorting a DataFrame (Enrichment)
df.sort_values(by='col')sorts rows by a column, in ascending order by default.ascending=Falsesorts in descending order (rank lists).by=['col1', 'col2']sorts by the first column and breaks ties with the second;ascendingcan then be a list, one entry per column.df.sort_index()sorts by row labels;sort_index(axis=1)sorts the columns by name.- NaN values go to the end by default.
Sorting returns a new DataFrame, so assign the result (df = df.sort_values(...)) to keep it.
import pandas as pd
df = pd.DataFrame({'Name': ['Aditi', 'Bharat', 'Chirag', 'Devika', 'Eshan', 'Fatima'],
'Section': ['A', 'B', 'A', 'C', 'B', 'C'],
'Total': [258, 197, 271, 233, 193, 248]})
print(df.sort_values(by='Total', ascending=False).head(3))
print()
print(df.sort_values(by=['Section', 'Total'], ascending=[True, False]))
print()
print(df.sort_index(ascending=False).head(2)) Name Section Total
2 Chirag A 271
0 Aditi A 258
5 Fatima C 248
Name Section Total
2 Chirag A 271
0 Aditi A 258
1 Bharat B 197
4 Eshan B 193
5 Fatima C 248
3 Devika C 233
Name Section Total
5 Fatima C 248
4 Eshan B 193Missing values: detecting them (Enrichment)
Real data has gaps: an absent student, a sensor that failed. Pandas marks a missing value as NaN (np.nan); None in a numeric column also becomes NaN.
df.isnull()(same asisna()) gives True where a value is missing;notnull()is the opposite.df.isnull().sum()counts missing values per column, anddf.isnull().sum().sum()gives the total.df.isnull().any()tells which columns have at least one missing value.df[df['col'].isnull()]shows the rows missing that value.
NaN is not equal to anything, not even itself, so df['col'] == np.nan never finds missing values. Always use isnull().
import pandas as pd
import numpy as np
ut = pd.DataFrame({'Maths': [18, np.nan, 22, 15],
'Science': [20, 17, np.nan, np.nan],
'English': [19, 21, 24, 16]},
index=['Aditi', 'Bharat', 'Chirag', 'Devika'])
print(ut.isnull())
print(ut.isnull().sum())
print(ut.isnull().sum().sum(), ut.isnull().any().tolist())
print(ut[ut['Science'].isnull()].index.tolist())
print(np.nan == np.nan)Maths Science English Aditi False False False Bharat True False False Chirag False True False Devika False True False Maths 1 Science 2 English 0 dtype: int64 3 [True, True, False] ['Chirag', 'Devika'] False
Missing values: dropping and filling (Enrichment)
Drop with dropna():
df.dropna()removes every row that has at least one NaN.dropna(how='all')removes a row only if all its values are NaN.dropna(axis=1)removes columns instead of rows.dropna(thresh=n)keeps rows that have at least n non-NaN values.
Fill with fillna():
df.fillna(0): one value everywhere.df.fillna({'Maths': 0, 'Science': 10}): a different value per column.df.fillna(df.mean(numeric_only=True)): each column's NaN replaced by that column's mean (an estimate).
Both return new DataFrames, so assign the result. Dropping loses data; filling invents data. Choose with care and say which you did.
import pandas as pd
import numpy as np
ut = pd.DataFrame({'Maths': [18, np.nan, 22, 15],
'Science': [20, 17, np.nan, np.nan],
'English': [19, 21, 24, 16]},
index=['Aditi', 'Bharat', 'Chirag', 'Devika'])
print(ut.dropna())
print(ut.dropna(axis=1))
print(ut.fillna(0))
print(ut.fillna(ut.mean()).round(1)) Maths Science English
Aditi 18.0 20.0 19
English
Aditi 19
Bharat 21
Chirag 24
Devika 16
Maths Science English
Aditi 18.0 20.0 19
Bharat 0.0 17.0 21
Chirag 22.0 0.0 24
Devika 15.0 0.0 16
Maths Science English
Aditi 18.0 20.0 19
Bharat 18.3 17.0 21
Chirag 22.0 18.5 24
Devika 15.0 18.5 16fillna(method='ffill') from older books is deprecated in Pandas 2.1 and later. If you need forward fill, use df.ffill().
Altering the index: set_index() and reset_index() (Enrichment)
df.set_index('Name')makes a column the row index.df.reset_index()moves the index back into a column and creates a fresh 0, 1, 2, ... index.reset_index(drop=True)throws the old index away. This is useful after filtering or sorting, when the old labels are no longer continuous.
Both return new DataFrames.
import pandas as pd
df = pd.DataFrame({'Name': ['Aditi', 'Bharat', 'Chirag', 'Devika'],
'Section': ['A', 'B', 'A', 'C'], 'Maths': [88, 67, 92, 75]})
secA = df[df['Section'] == 'A']
print(secA)
print(secA.reset_index(drop=True))
print(df.set_index('Name').loc['Devika', 'Maths']) Name Section Maths
0 Aditi A 88
2 Chirag A 92
Name Section Maths
0 Aditi A 88
1 Chirag A 92
75Reshaping with pivot_table() (Enrichment)
pd.pivot_table(df, index=..., columns=..., values=..., aggfunc=...) rearranges long data into a grid: the unique values of index become rows, those of columns become columns, and each cell holds aggfunc (for example 'sum') of values for that combination. Missing combinations are NaN, or the value given in fill_value.
df.pivot() does a similar job but fails if any combination appears twice, so pivot_table() is the safer choice.
import pandas as pd
sales = pd.read_csv('sales.csv')
grid = pd.pivot_table(sales, index='Category', columns='Quarter',
values='Expenditure', aggfunc='sum', fill_value=0)
print(grid)Quarter Q1 Q2 Q3 Q4 Category Lab 26000 31500 0 64000 Sports 42000 12000 15500 0 Stationery 18500 6500 22000 17000
Connecting Pandas to MySQL
Data in schools and businesses usually lives in a database. Pandas can read a MySQL table into a DataFrame (import) and write a DataFrame back as a table (export). Two extra libraries are needed, installed once from the command prompt:
pip install pymysql: the driver that talks to MySQLpip install sqlalchemy: providescreate_engine()
create_engine() takes a connection string and returns an engine object that Pandas uses for every read or write:
'mysql+pymysql://username:password@host:port/database'
Here mysql+pymysql names the database and driver, host is usually localhost, and the default MySQL port is 3306.
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://root:tiger@localhost:3306/school')Pandas 1.x does not work with SQLAlchemy 2.0. On older lab computers install pip install "sqlalchemy<2.0". Also check that the MySQL server is running and the database already exists; to_sql() creates tables but not databases.
The student table used in the MySQL examples
The Pandas examples that follow use a table student in the MySQL database school. Create it at the mysql> prompt with the SQL from Unit 2:
CREATE DATABASE school;
USE school;
CREATE TABLE student (RollNo INT PRIMARY KEY, Name VARCHAR(20), Section CHAR(3), Marks INT);
Insert the six rows, then check the table:
SELECT * FROM student;+--------+--------+---------+-------+ | RollNo | Name | Section | Marks | +--------+--------+---------+-------+ | 1201 | Aditi | 12A | 88 | | 1202 | Bharat | 12B | 67 | | 1203 | Chirag | 12A | 92 | | 1204 | Devika | 12C | 75 | | 1205 | Eshan | 12B | 59 | | 1206 | Fatima | 12C | 81 | +--------+--------+---------+-------+ 6 rows in set (0.00 sec)
Importing data from MySQL: read_sql_query(), read_sql_table(), read_sql()
Using the student table above, Pandas offers three functions, each returning a DataFrame:
pd.read_sql_query(query, engine): runs a SELECT query and loads its result.pd.read_sql_table('table', engine): loads a whole table (needs SQLAlchemy).pd.read_sql(query_or_table, engine): accepts either a query or a table name.
Select only what you need with a WHERE clause in the query, then analyse it in Pandas.
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://root:tiger@localhost:3306/school')
df = pd.read_sql_query('SELECT * FROM student', engine)
print(df)
top = pd.read_sql_query('SELECT Name, Marks FROM student WHERE Marks > 80', engine)
print(top)
print(df.groupby('Section')['Marks'].mean()) RollNo Name Section Marks
0 1201 Aditi 12A 88
1 1202 Bharat 12B 67
2 1203 Chirag 12A 92
3 1204 Devika 12C 75
4 1205 Eshan 12B 59
5 1206 Fatima 12C 81
Name Marks
0 Aditi 88
1 Chirag 92
2 Fatima 81
Section
12A 90.0
12B 63.0
12C 78.0
Name: Marks, dtype: float64A MySQL DECIMAL column arrives in Pandas as Python Decimal objects with dtype object. Convert with df['Fee'] = df['Fee'].astype(float) before doing statistics. INT and FLOAT columns arrive as numbers.
Exporting data to MySQL: to_sql()
df.to_sql('table', engine, if_exists='fail', index=True) writes a DataFrame into a MySQL table.
if_exists='fail'(default): raiseValueErrorif the table already exists.if_exists='replace': drop the old table and create it again from the DataFrame.if_exists='append': add the rows to the existing table; the column names must match.index=False: do not write the DataFrame's row labels as a column (usually what you want).
The table is created automatically with column types chosen from the dtypes.
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://root:tiger@localhost:3306/school')
clubs = pd.DataFrame({'ClubId': [1, 2, 3],
'Club': ['Robotics', 'Eco', 'Quiz'],
'Members': [24, 31, 18]})
clubs.to_sql('club', engine, if_exists='replace', index=False)
more = pd.DataFrame({'ClubId': [4], 'Club': ['Music'], 'Members': [27]})
more.to_sql('club', engine, if_exists='append', index=False)
print(pd.read_sql_query('SELECT * FROM club', engine))ClubId Club Members 0 1 Robotics 24 1 2 Eco 31 2 3 Quiz 18 3 4 Music 27
Key terms
- Descriptive statistics
- Numbers that summarise data, such as mean, median, mode, range and standard deviation.
- Mean
- Sum of the values divided by their number;
mean(). - Median
- Middle value of sorted data (average of the two middle values for an even count);
median(). - Mode
- Most frequent value;
mode()returns all modes as a Series. - Quantile
- Value below which a given fraction of the data lies;
quantile(q). - Percentile
- A quantile expressed out of 100; the 75th percentile is
quantile(0.75). - Quartile
- The 25th, 50th and 75th percentiles, which split data into four equal parts.
- Variance
- Average squared distance of values from the mean (Pandas uses n - 1);
var(). - Standard deviation
- Square root of the variance, a measure of spread in the data's own units;
std(). - Aggregation
- Reducing many values to one, e.g. sum, mean or count;
agg()applies several at once. - GroupBy
- Splitting rows into groups by a column's values, applying a function to each group and combining the results.
- Missing value
- A value that is not available, shown as NaN in Pandas.
- Imputation
- Replacing missing values with an estimate such as 0 or the column mean, using
fillna(). - Pivot table
- A grid that summarises data with one column's values as rows, another's as columns and an aggregate in each cell.
- Connection string
- Text such as
mysql+pymysql://user:pwd@localhost:3306/dbthat tells SQLAlchemy how to reach a database. - SQLAlchemy engine
- The object returned by
create_engine()that Pandas uses to read from and write to a database. - pymysql
- A Python driver library that lets Python programs talk to a MySQL server.
MCQs
Tap an option to check it, or use Show answers above to reveal them all.
-
s = pd.Series([10, 20, 30, 40, 50]). What is printed byprint(s.quantile(0.75))?Answer(a) 40.0
Position = (5 - 1) × 0.75 = 3, which is exactly the 4th value, 40.
-
s = pd.Series([2, 4, 6, 8]). What is printed byprint(s.quantile(0.5))?Answer(c) 5.0
The 0.5 quantile is the median: the average of 4 and 6.
-
Which statement prints the elements of Series
sthat are above the 75th percentile?Answer(b)
print(s[s > s.quantile(0.75)])The comparison makes a Boolean Series and the outer brackets filter with it. Option 1 prints only True/False values;
quantile(75)is invalid because q must be between 0 and 1. -
What does
df.groupby('City')['Sales'].sum()return?Answer(c) A Series with the total sales for each city
Rows are grouped by City, Sales is summed in each group, and the city names become the index of the resulting Series.
-
Which method gives the number of rows in each group, including rows with NaN?
Answer(d)
size()size()counts rows;count()counts non-NaN values in each column. -
s = pd.Series([3, 7, 3, 9, 7, 1]). What is printed byprint(s.mode().tolist())?Answer(a)
[3, 7]3 and 7 each occur twice, so both are modes, in ascending order.
-
s = pd.Series([12, 5, 9, 20]). What is printed byprint(s.median())?Answer(c) 10.5
Sorted: 5, 9, 12, 20. The median is the average of 9 and 12, i.e. 10.5.
-
A DataFrame has a text column Name and numeric columns. Which call works in Pandas 1.x, 2.x and 3.x to get the mean of each numeric column?
Answer(d)
df.mean(numeric_only=True)numeric_only=Trueskips text columns. Without it, Pandas 2.0+ raises TypeError; there is noaverage()method. -
Which function returns count, mean, std, min, quartiles and max of numeric columns in one call?
Answer(a)
describe()describe()produces this summary table. -
What does
df.sum(axis=1)compute?Answer(b) The total of each row
axis=1works across columns, giving one total per row (for example each student's total marks). -
s = pd.Series([4, None, 8, None, 6]). What is printed byprint(s.count(), s.size)?Answer(c) 3 5
count()ignores the two missing values;sizecounts all five elements. -
Which statement sorts DataFrame
dfin descending order of the column Marks?Answer(a)
df.sort_values(by='Marks', ascending=False)sort_valuessorts by column values; descending order isascending=False. -
Which statement counts the missing values in each column of
df?Answer(a)
df.isnull().sum()isnull()gives True for NaN; summing True values counts them.isnull().count()counts all cells. -
What does
df.dropna(how='all')do?Answer(d) Drops rows in which every value is NaN
With
how='all', a row is removed only when all of its values are missing. -
s = pd.Series([10, None, 30]). What is printed byprint(s.fillna(s.mean()).tolist())?Answer(b)
[10.0, 20.0, 30.0]The mean skips NaN: (10 + 30) / 2 = 20, which then fills the gap.
-
Which library provides
create_engine()for connecting Pandas to MySQL?Answer(b) sqlalchemy
create_engine()comes from SQLAlchemy; pymysql is the driver it uses underneath. -
In
create_engine('mysql+pymysql://root:abc@localhost:3306/shop'), what isshop?Answer(d) The database name
The format is
driver://user:password@host:port/database. -
Which call reads the result of an SQL SELECT statement into a DataFrame?
Answer(a)
pd.read_sql_query(query, engine)read_sql_query()runs the query through the engine and returns a DataFrame. -
df.to_sql('marks', engine, if_exists='append', index=False)is run when the tablemarksalready exists. What happens?Answer(c) The rows of
dfare added to the tableappendinserts the rows into the existing table.failraises ValueError andreplacerecreates the table. -
What is the default value of
if_existsinto_sql()?Answer(d) 'fail'
By default
to_sql()refuses to touch an existing table and raises ValueError. -
dfhas columns Item and Price. What is printed byprint(df.groupby('Item')['Price'].max().loc['Pen'])if the Pen rows have prices 10, 25 and 15?Answer(c) 25
The largest Pen price is 25.
-
Which statement is true about
std()in Pandas?Answer(b) It divides by n - 1 by default
Pandas uses the sample formula (
ddof=1). The standard deviation is the square root of the variance.
Assertion–Reason
Assertion (A):
s.mode()can return more than one value.Reason (R): A data set can have two or more values that occur with the same highest frequency.
Answer(A)
Because several values can tie for most frequent, Pandas returns all of them as a Series.
Assertion (A):
df.mean()raises TypeError in Pandas 2.x whendfcontains a column of names.Reason (R): From Pandas 2.0,
numeric_onlydefaults to False, so text columns are included in the calculation.Answer(A)
Pandas tries to average the names and fails; R explains why. Passing
numeric_only=Trueavoids it.Assertion (A):
s.quantile(0.75)always returns one of the values present ins.Reason (R): By default Pandas uses linear interpolation between the two nearest values.
Answer(D)
A is false: interpolation can produce a value between two data points, e.g. 86.75. R is true.
Assertion (A):
df.count()may return a smaller number thanlen(df)for a column.Reason (R):
count()does not count NaN values.Answer(A)
Missing values are skipped by
count()but are still rows, so the count can be smaller.Assertion (A):
df.to_sql('result', engine)run twice raises an error the second time.Reason (R):
to_sql()writes the DataFrame index as a column by default.Answer(B)
Both are true. The error comes from the default
if_exists='fail', not from the index, so R does not explain A.
Short answer
- 2 marks
Write the output:
s = pd.Series([15, 30, 45, 60, 75])print(s.quantile(0.25), s.quantile(0.75))print(s[s > s.quantile(0.75)])AnswerWith 5 values, positions for 0.25 and 0.75 are 1 and 3, so Q1 = 30 and Q3 = 60. Only 75 lies above 60.
Text 30.0 60.0 4 75 dtype: int64 - 2 marks
Write the output:
s = pd.Series([4, 9, 4, 7, 9, 4])print(s.mode())print(s.mean(), s.median())Answer4 occurs three times, so it is the only mode. Mean = 37 / 6. Sorted: 4, 4, 4, 7, 9, 9; median = (4 + 7) / 2.
Text 0 4 dtype: int64 6.166666666666667 5.5 - 2 marks
Differentiate between
count()andsizewith an example.Answercount()is a method that returns the number of non-NaN values;sizeis an attribute that returns the total number of elements including NaN. For a Series of 5, NaN, 7:Text 2 3 - 2 marks
Write the output:
df = pd.DataFrame({'A': [1, 2, 3], 'B': [10, 20, 30]})print(df.sum())print(df.sum(axis=1).tolist())AnswerBy default sums are column-wise;
axis=1sums each row.Text A 6 B 60 dtype: int64 [11, 22, 33] - 2 marks
A DataFrame
saleshas columns Region, Product and Amount. Write statements to display (a) total Amount per Region and (b) the number of sales per Product.Answer(a)
print(sales.groupby('Region')['Amount'].sum())(b)
print(sales.groupby('Product').size())(orsales.groupby('Product')['Amount'].count()) - 2 marks
Write the output:
df = pd.DataFrame({'House': ['Red', 'Blue', 'Red', 'Blue', 'Red'], 'Pts': [5, 8, 3, 6, 4]})print(df.groupby('House')['Pts'].sum())AnswerBlue = 8 + 6 and Red = 5 + 3 + 4. Group labels are sorted and become the index; the Series is named after the column.
Text House Blue 14 Red 12 Name: Pts, dtype: int64 - 2 marks
Write the statement to sort DataFrame
empby Dept in ascending order and, within each department, by Salary in descending order.Answeremp = emp.sort_values(by=['Dept', 'Salary'], ascending=[True, False]) - 2 marks
What is the difference between
dropna()andfillna()? Give one example of each.Answerdropna()removes rows (or columns withaxis=1) that contain missing values, e.g.df = df.dropna().fillna()replaces missing values with a given value, e.g.df = df.fillna(0)ordf['Marks'] = df['Marks'].fillna(df['Marks'].mean()). - 2 marks
Write the output:
s = pd.Series([8, None, 4, None])print(s.isnull().sum())print(s.fillna(0).sum())AnswerTwo values are missing. After filling with 0 the sum is 12, shown as a float because the Series became float64 when None was converted to NaN.
Text 2 12.0 - 2 marks
Why does
mode()return a Series and not a single value? How will you get one value?AnswerData can have more than one most-frequent value (for example 3 and 7 both appearing twice), so Pandas returns all modes as a Series sorted in ascending order. Use
s.mode()[0](ors.mode().iloc[0]) to take the first one. - 2 marks
Name the two libraries to be installed to connect Pandas with MySQL, and write the statement that creates the connection to database
libraryon the local computer for userrootwith passwordbook123.Answerpymysql(driver) andsqlalchemy(providescreate_engine).engine = create_engine('mysql+pymysql://root:book123@localhost:3306/library') - 2 marks
Explain the three values of the
if_existsparameter ofto_sql().Answer'fail'(default): raise ValueError if the table already exists.'replace': drop the existing table and create a new one with the DataFrame's data.'append': insert the DataFrame's rows into the existing table.
- 2 marks
Write the output:
s = pd.Series([40, 42, 38, 40])print(s.var(), s.std())AnswerMean = 40. Squared differences: 0, 4, 4, 0, total 8. Pandas divides by n - 1 = 3, so variance = 8/3 ≈ 2.667, and std is its square root.
Text 2.6666666666666665 1.632993161855452
Long answer
- 3 marks
Explain the split-apply-combine idea behind
groupby(). For the DataFramesales(columns Quarter, Category, Item, Expenditure), write statements to print (a) total expenditure per category, (b) the average expenditure per quarter, and (c) the category with the highest total.AnswerSplit the rows into groups by a column's values, apply a function (sum, mean, ...) to each group, and combine the answers into one result whose index is the group labels.
Python import pandas as pd sales = pd.read_csv('sales.csv') cat = sales.groupby('Category')['Expenditure'].sum() print(cat) print(sales.groupby('Quarter')['Expenditure'].mean()) print('Highest:', cat.idxmax())OutputCategory Lab 121500 Sports 69500 Stationery 64000 Name: Expenditure, dtype: int64 Quarter Q1 28833.333333 Q2 16666.666667 Q3 18750.000000 Q4 40500.000000 Name: Expenditure, dtype: float64 Highest: Lab
- 5 marks
Write a program to create a Series of 12 monthly electricity bills (in rupees) of a house and display: the mean, median, highest and lowest bill, the month of the highest bill, and the months whose bill is above the 75th percentile.
AnswerThe months are used as the index, so
idxmax()returns the month and the filter keeps month labels.Python import pandas as pd bills = pd.Series([1450, 1380, 1720, 2650, 3480, 3910, 3020, 2780, 2410, 1890, 1560, 1620], index=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']) print('Mean:', round(bills.mean(), 2), 'Median:', bills.median()) print('Highest:', bills.max(), 'in', bills.idxmax(), '| Lowest:', bills.min()) q3 = bills.quantile(0.75) print('75th percentile:', q3) print(bills[bills > q3])OutputMean: 2322.5 Median: 2150.0 Highest: 3910 in Jun | Lowest: 1380 75th percentile: 2840.0 May 3480 Jun 3910 Jul 3020 dtype: int64
- 3 marks
A unit-test sheet has missing marks for absent students. Explain three ways of handling missing values in Pandas with code, and state when each is suitable.
Answer- Drop the rows (
dropna()): when only a few rows are incomplete and they can be left out of the analysis. - Fill with a fixed value (
fillna(0)): when missing truly means zero, e.g. an absent student scored nothing. - Fill with an estimate (
fillna(df.mean())): when the value exists but was not recorded and an approximate value is acceptable.
Python import pandas as pd import numpy as np ut = pd.DataFrame({'Maths': [18, np.nan, 22, 15], 'Science': [20, 17, np.nan, 19]}, index=['Aditi', 'Bharat', 'Chirag', 'Devika']) print(ut.dropna()) print(ut.fillna(0)) print(ut.fillna(ut.mean()).round(2))OutputMaths Science Aditi 18.0 20.0 Devika 15.0 19.0 Maths Science Aditi 18.0 20.0 Bharat 0.0 17.0 Chirag 22.0 0.0 Devika 15.0 19.0 Maths Science Aditi 18.00 20.00 Bharat 18.33 17.00 Chirag 22.00 18.67 Devika 15.00 19.00 - Drop the rows (
- 5 marks
Write a program that reads the
studenttable (RollNo, Name, Section, Marks) of MySQL databaseschoolinto a DataFrame, adds a column Grade ('A' for marks 80 and above, otherwise 'B'), and writes the result to a new MySQL tablestudent_gradewithout the index. Explain each step.Answercreate_engine()builds the connection from the connection string.read_sql_query()runs the SELECT and returns a DataFrame.- A list comprehension over the Marks column builds the Grade column.
to_sql()withif_exists='replace'creates (or recreates) the table;index=Falsekeeps the 0, 1, 2 labels out of it.
The final
printreads the new table back to check it.Python import pandas as pd from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://root:tiger@localhost:3306/school') df = pd.read_sql_query('SELECT * FROM student', engine) df['Grade'] = ['A' if m >= 80 else 'B' for m in df['Marks']] df.to_sql('student_grade', engine, if_exists='replace', index=False) print(pd.read_sql_query('SELECT * FROM student_grade', engine))OutputRollNo Name Section Marks Grade 0 1201 Aditi 12A 88 A 1 1202 Bharat 12B 67 B 2 1203 Chirag 12A 92 A 3 1204 Devika 12C 75 B 4 1205 Eshan 12B 59 B 5 1206 Fatima 12C 81 A
- 3 marks
Write the output of the following code:
df = pd.DataFrame({'Name': ['Ira', 'Om', 'Pia', 'Raj'], 'Sec': ['A', 'B', 'A', 'B'], 'Marks': [72, 85, 90, 64]})print(df.sort_values(by='Marks', ascending=False).head(2))print(df.groupby('Sec')['Marks'].mean())print(df['Marks'].describe()[['min', 'max']])AnswerSorting keeps the original row labels (2 and 1). Section A's mean is (72 + 90) / 2 = 81 and B's is (85 + 64) / 2 = 74.5.
describe()returns floats, so min and max print as 64.0 and 90.0.Text Name Sec Marks 2 Pia A 90 1 Om B 85 Sec A 81.0 B 74.5 Name: Marks, dtype: float64 min 64.0 max 90.0 Name: Marks, dtype: float64 - 3 marks
Compare
mean,medianandmodeas measures of the "typical" value, using the daily wages (in rupees) of seven workers: 450, 480, 450, 500, 470, 450, 2500. Write code to compute all three and comment on which best represents the group.AnswerOne very high wage (2500) pulls the mean up to about 757, which no worker actually earns near. The median (470) and the mode (450) ignore the extreme value, so they describe a typical worker better. The median is usually the best single measure for data with outliers.
Python import pandas as pd wages = pd.Series([450, 480, 450, 500, 470, 450, 2500]) print(round(wages.mean(), 2)) print(wages.median()) print(wages.mode()[0])Output757.14 470.0 450
- 5 marks
Write a program to create a DataFrame of six cricket players (Name, Team, Runs, Matches) using sample data. Display (a) the
describe()summary, (b) players sorted by Runs (highest first), (c) team-wise total runs and number of players usingagg(), (d) players whose Runs are above the 75th percentile.Answerdescribe()summarises the numeric columns Runs and Matches; sorting,agg()and a quantile filter answer the other parts.Python import pandas as pd ipl = pd.DataFrame({'Name': ['Kohli', 'Gill', 'Jaiswal', 'Rinku', 'Samson', 'Gaikwad'], 'Team': ['RCB', 'GT', 'RR', 'KKR', 'RR', 'CSK'], 'Runs': [741, 426, 435, 168, 531, 583], 'Matches': [15, 12, 16, 14, 16, 14]}) print(ipl.describe()) print(ipl.sort_values(by='Runs', ascending=False)[['Name', 'Runs']]) print(ipl.groupby('Team')['Runs'].agg(['sum', 'count'])) print(ipl[ipl['Runs'] > ipl['Runs'].quantile(0.75)]['Name'].tolist())OutputRuns Matches count 6.000000 6.000000 mean 480.666667 14.500000 std 191.631591 1.516575 min 168.000000 12.000000 25% 428.250000 14.000000 50% 483.000000 14.500000 75% 570.000000 15.750000 max 741.000000 16.000000 Name Runs 0 Kohli 741 5 Gaikwad 583 4 Samson 531 2 Jaiswal 435 1 Gill 426 3 Rinku 168 sum count Team CSK 583 1 GT 426 1 KKR 168 1 RCB 741 1 RR 966 2 ['Kohli', 'Gaikwad']
Case-based
The school canteen records every purchase in canteen.csv with columns Day, Item, Category (Snack/Drink/Meal) and Amount. The manager loads it with cn = pd.read_csv('canteen.csv') and wants a weekly report.
- 1 mark
Write a statement to display the total Amount for each Category.
Answerprint(cn.groupby('Category')['Amount'].sum()) - 1 mark
Write a statement to display the number of purchases made on each Day.
Answerprint(cn.groupby('Day').size()) - 1 mark
Write a statement to display the five most expensive purchases.
Answerprint(cn.sort_values(by='Amount', ascending=False).head(5)) - 1 mark
Write statements to find the 75th percentile of Amount and display only the purchases above it.
Answerq3 = cn['Amount'].quantile(0.75)print(cn[cn['Amount'] > q3])
Ms. Rekha has the half-yearly marks of her class in a DataFrame:
hy = pd.DataFrame({'Maths': [78, None, 91, 66], 'Physics': [None, 58, 84, 70], 'Chem': [81, 62, 88, None]}, index=['Neel', 'Ojas', 'Prisha', 'Qadir'])
Blank cells are students who were absent.
- 1 mark
Write the output of
print(hy.isnull().sum()).AnswerEach column has exactly one missing mark.
Text Maths 1 Physics 1 Chem 1 dtype: int64 - 1 mark
Write the output of
print(hy.dropna()).AnswerOnly Prisha has no missing mark. The values are floats because each column contained NaN.
Text Maths Physics Chem Prisha 91.0 84.0 88.0 - 1 mark
Write a statement to replace the missing marks with 0 and store the result back in
hy.Answerhy = hy.fillna(0) - 1 mark
After filling with 0, write the output of
print(hy.mean(axis=1)).Answeraxis=1averages each student's three marks; the 0s pull down the averages of the absent students.Text Neel 53.000000 Ojas 40.000000 Prisha 87.666667 Qadir 45.333333 dtype: float64
A bookshop in Kolkata keeps its stock in a MySQL database bookshop, table books (BookId, Title, Genre, Price, Qty). The owner's son, a Class XII student, wants to analyse it with Pandas. MySQL runs on the same computer; user root, password kol2025.
- 1 mark
Write the import statements and the statement that creates the engine.
Answerimport pandas as pdfrom sqlalchemy import create_engineengine = create_engine('mysql+pymysql://root:kol2025@localhost:3306/bookshop') - 1 mark
Write a statement to load only the books of genre 'Fiction' into DataFrame
fic.Answerfic = pd.read_sql_query("SELECT * FROM books WHERE Genre = 'Fiction'", engine) - 1 mark
Write a statement to load the full table and display the total quantity per Genre.
Answerbk = pd.read_sql_table('books', engine)(orpd.read_sql('SELECT * FROM books', engine))print(bk.groupby('Genre')['Qty'].sum()) - 1 mark
He adds a column
Value = Price * Qtyin Pandas. Write a statement to savebkas a new tablestock_value, replacing it if it already exists and without the index.Answerbk.to_sql('stock_value', engine, if_exists='replace', index=False)
Practicals
1Elements above the 75th percentile
Given a Series of the runs scored by a batter in 12 innings, print the 75th percentile and all the elements that are above it. Also print how many innings these are. (CBSE suggested practical 2.)
import pandas as pd
runs = pd.Series([34, 12, 87, 45, 102, 8, 66, 59, 23, 91, 40, 77],
index=['I' + str(i) for i in range(1, 13)])
q3 = runs.quantile(0.75)
print('75th percentile:', q3)
above = runs[runs > q3]
print(above)
print('Innings above 75th percentile:', above.count())75th percentile: 79.5 I3 87 I5 102 I10 91 dtype: int64 Innings above 75th percentile: 3
Viva questions (3)
- What value of q gives the 75th percentile? 0.75.
- What is another name for the 75th percentile? The third quartile (Q3).
- Why can the 75th percentile be a decimal for whole-number data? Pandas interpolates between two sorted values.
2Quarterly sales: total expenditure per category
Create a DataFrame of quarterly sales where each row contains the item category, item name and expenditure (read sales.csv). Group the rows by category and print the total expenditure per category, highest first. (CBSE suggested practical 3.)
import pandas as pd
sales = pd.read_csv('sales.csv')
print(sales[['Category', 'Item', 'Expenditure']])
total = sales.groupby('Category')['Expenditure'].sum()
print(total.sort_values(ascending=False))Category Item Expenditure 0 Stationery Registers 18500 1 Sports Cricket kits 42000 2 Lab Chemicals 26000 3 Stationery Chalk boxes 6500 4 Lab Glassware 31500 5 Sports Footballs 12000 6 Stationery Printer paper 22000 7 Sports Badminton sets 15500 8 Lab Microscopes 64000 9 Stationery Registers 17000 Category Lab 121500 Sports 69500 Stationery 64000 Name: Expenditure, dtype: int64
Viva questions (3)
- What becomes the index of the result of
groupby()? The group values (categories). - Why do we select the Expenditure column before
sum()? To avoid summing text columns. - Which SQL clause does
groupby()correspond to? GROUP BY.
3Group-wise summary with agg()
For the same quarterly sales data, print for each category: total, average and maximum expenditure and the number of items bought. Then print the total expenditure per quarter and category, with the group labels as ordinary columns.
import pandas as pd
sales = pd.read_csv('sales.csv')
print(sales.groupby('Category')['Expenditure'].agg(['sum', 'mean', 'max', 'count']))
qc = sales.groupby(['Quarter', 'Category'])['Expenditure'].sum().reset_index()
print(qc)sum mean max count Category Lab 121500 40500.000000 64000 3 Sports 69500 23166.666667 42000 3 Stationery 64000 16000.000000 22000 4 Quarter Category Expenditure 0 Q1 Lab 26000 1 Q1 Sports 42000 2 Q1 Stationery 18500 3 Q2 Lab 31500 4 Q2 Sports 12000 5 Q2 Stationery 6500 6 Q3 Sports 15500 7 Q3 Stationery 22000 8 Q4 Lab 64000 9 Q4 Stationery 17000
Viva questions (3)
- What does
agg()take? A list (or dictionary) of functions to apply to each group. - What does
reset_index()do after a groupby? Moves the group labels back into columns. - How many index levels does grouping on two columns give? Two.
4Exam result: attributes and statistics
Create a DataFrame for the examination result of six students (Name, Maths, Science, English). Display the row labels, column labels, data types and dimensions, and then the subject-wise highest, lowest, mean and median marks, each student's total, and the topper's name.
import pandas as pd
res = pd.DataFrame({'Name': ['Aditi', 'Bharat', 'Chirag', 'Devika', 'Eshan', 'Fatima'],
'Maths': [88, 67, 92, 75, 59, 81],
'Science': [79, 72, 95, 81, 64, 81],
'English': [91, 58, 84, 77, 70, 86]})
print(res.index.tolist(), res.columns.tolist())
print(res.dtypes)
print('Shape:', res.shape)
marks = res[['Maths', 'Science', 'English']]
print(pd.DataFrame({'Max': marks.max(), 'Min': marks.min(),
'Mean': marks.mean().round(2), 'Median': marks.median()}))
res['Total'] = marks.sum(axis=1)
print(res[['Name', 'Total']])
print('Topper:', res.loc[res['Total'].idxmax(), 'Name'])[0, 1, 2, 3, 4, 5] ['Name', 'Maths', 'Science', 'English']
Name object
Maths int64
Science int64
English int64
dtype: object
Shape: (6, 4)
Max Min Mean Median
Maths 92 59 77.00 78.0
Science 95 64 78.67 80.0
English 91 58 77.67 80.5
Name Total
0 Aditi 258
1 Bharat 197
2 Chirag 271
3 Devika 233
4 Eshan 193
5 Fatima 248
Topper: ChiragViva questions (3)
- What does
idxmax()return? The label of the maximum value. - Why select
marksbeforemax()? To keep the Name column out of numeric statistics. - Which axis gives each student's total?
axis=1.
5describe() and quartiles of a class
For the marks of 10 students in Maths and Science, display the describe() summary, the first and third quartiles of each subject, and the names of students above the 75th percentile in Maths.
import pandas as pd
df = pd.DataFrame({'Name': ['Asha', 'Bilal', 'Charu', 'Dhruv', 'Ekta',
'Faiz', 'Gauri', 'Harsh', 'Indu', 'Jatin'],
'Maths': [56, 78, 91, 45, 67, 88, 72, 95, 60, 83],
'Science': [61, 74, 89, 52, 70, 81, 77, 92, 58, 85]})
print(df.describe())
print(df.quantile([0.25, 0.75], numeric_only=True))
print(df[df['Maths'] > df['Maths'].quantile(0.75)]['Name'].tolist()) Maths Science
count 10.000000 10.000000
mean 73.500000 73.900000
std 16.446884 13.551958
min 45.000000 52.000000
25% 61.750000 63.250000
50% 75.000000 75.500000
75% 86.750000 84.000000
max 95.000000 92.000000
Maths Science
0.25 61.75 63.25
0.75 86.75 84.00
['Charu', 'Faiz', 'Harsh']Viva questions (3)
- Which row of
describe()shows the median? The 50% row. - Why is
numeric_only=Truepassed toquantile()? The Name column is text. - Does
stdindescribe()divide by n or n - 1? n - 1.
6Sorting: rank list
Create a DataFrame of eight students with Name, Section and Percent. Display (a) a rank list in descending order of Percent with a Rank column 1, 2, 3, ..., (b) the top three students, and (c) students sorted by Section and, within a section, by Percent (highest first).
import pandas as pd
df = pd.DataFrame({'Name': ['Kiran', 'Laila', 'Manav', 'Nidhi', 'Omar', 'Prachi', 'Rehan', 'Simran'],
'Section': ['B', 'A', 'A', 'C', 'B', 'C', 'A', 'B'],
'Percent': [81.5, 92.4, 67.0, 88.8, 74.2, 95.1, 79.9, 86.3]})
rank = df.sort_values(by='Percent', ascending=False).reset_index(drop=True)
rank.insert(0, 'Rank', range(1, len(rank) + 1))
print(rank)
print(rank.head(3))
print(df.sort_values(by=['Section', 'Percent'], ascending=[True, False])) Rank Name Section Percent
0 1 Prachi C 95.1
1 2 Laila A 92.4
2 3 Nidhi C 88.8
3 4 Simran B 86.3
4 5 Kiran B 81.5
5 6 Rehan A 79.9
6 7 Omar B 74.2
7 8 Manav A 67.0
Rank Name Section Percent
0 1 Prachi C 95.1
1 2 Laila A 92.4
2 3 Nidhi C 88.8
Name Section Percent
1 Laila A 92.4
6 Rehan A 79.9
2 Manav A 67.0
7 Simran B 86.3
0 Kiran B 81.5
4 Omar B 74.2
5 Prachi C 95.1
3 Nidhi C 88.8Viva questions (3)
- Which parameter sorts in descending order?
ascending=False. - Why is
reset_index(drop=True)used? To number the sorted rows afresh and drop the old labels. - How do you sort by two columns? Pass a list to
by.
7Handling missing values
A DataFrame stores the weekly attendance (days present out of 6) of five students for four weeks; NaN means the register page was missing. Display the number of missing entries per week and in total, the rows with any missing entry, the data with missing entries filled with each week's mean (rounded to 1 decimal place), and the data after dropping students with more than one missing week.
import pandas as pd
import numpy as np
att = pd.DataFrame({'W1': [6, 5, np.nan, 6, 4],
'W2': [5, np.nan, np.nan, 6, 5],
'W3': [6, 6, 4, np.nan, 5],
'W4': [6, 5, 5, 6, np.nan]},
index=['Aryan', 'Bhavya', 'Charvi', 'Daksh', 'Eva'])
print(att.isnull().sum())
print('Total missing:', att.isnull().sum().sum())
print(att[att.isnull().any(axis=1)])
print(att.fillna(att.mean()).round(1))
print(att.dropna(thresh=3))W1 1
W2 2
W3 1
W4 1
dtype: int64
Total missing: 5
W1 W2 W3 W4
Bhavya 5.0 NaN 6.0 5.0
Charvi NaN NaN 4.0 5.0
Daksh 6.0 6.0 NaN 6.0
Eva 4.0 5.0 5.0 NaN
W1 W2 W3 W4
Aryan 6.0 5.0 6.0 6.0
Bhavya 5.0 5.3 6.0 5.0
Charvi 5.2 5.3 4.0 5.0
Daksh 6.0 6.0 5.2 6.0
Eva 4.0 5.0 5.0 5.5
W1 W2 W3 W4
Aryan 6.0 5.0 6.0 6.0
Bhavya 5.0 NaN 6.0 5.0
Daksh 6.0 6.0 NaN 6.0
Eva 4.0 5.0 5.0 NaNViva questions (3)
- What does
isnull().any(axis=1)give? True for each row that has at least one NaN. - What does
thresh=3mean indropna()? Keep rows with at least 3 non-NaN values. - Does
mean()include NaN? No, it skips NaN.
8Filter duplicates, summarise and export to CSV
Read the quarterly sales file, remove duplicate items (same Category and Item, keeping the first entry), filter rows where Expenditure is above the overall mean, and export the category totals of the cleaned data to category_total.csv without the index. Read the file back to check it. (CBSE suggested practicals 5 and 6.)
import pandas as pd
sales = pd.read_csv('sales.csv')
print(sales[sales.duplicated(subset=['Category', 'Item'])])
clean = sales.drop_duplicates(subset=['Category', 'Item'])
print(clean[clean['Expenditure'] > clean['Expenditure'].mean()])
tot = clean.groupby('Category')['Expenditure'].sum().reset_index()
tot.to_csv('category_total.csv', index=False)
print(pd.read_csv('category_total.csv')) Quarter Category Item Expenditure
9 Q4 Stationery Registers 17000
Quarter Category Item Expenditure
1 Q1 Sports Cricket kits 42000
4 Q2 Lab Glassware 31500
8 Q4 Lab Microscopes 64000
Category Expenditure
0 Lab 121500
1 Sports 69500
2 Stationery 47000Viva questions (3)
- Which row was treated as a duplicate? The second Registers entry under Stationery.
- What does
subsetdo induplicated()? Compares only the listed columns. - Why is
reset_index()used beforeto_csv()? So Category is written as a normal column.
9Import a MySQL table into Pandas
The MySQL database school has a table student (RollNo, Name, Section, Marks). Read the whole table into a DataFrame, display it, display the section-wise highest and average marks, and read only the students with marks below 70 using an SQL query.
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://root:tiger@localhost:3306/school')
df = pd.read_sql_query('SELECT * FROM student', engine)
print(df)
print(df.groupby('Section')['Marks'].agg(['max', 'mean']))
low = pd.read_sql_query('SELECT RollNo, Name, Marks FROM student WHERE Marks < 70', engine)
print(low) RollNo Name Section Marks
0 1201 Aditi 12A 88
1 1202 Bharat 12B 67
2 1203 Chirag 12A 92
3 1204 Devika 12C 75
4 1205 Eshan 12B 59
5 1206 Fatima 12C 81
max mean
Section
12A 92 90.0
12B 67 63.0
12C 81 78.0
RollNo Name Marks
0 1202 Bharat 67
1 1205 Eshan 59Viva questions (3)
- Which two libraries must be installed? pymysql and sqlalchemy.
- What does
read_sql_query()return? A DataFrame. - What is the default port of MySQL? 3306.
10Export a DataFrame to MySQL
Create a DataFrame of school house points (House, Captain, Points). Write it to a MySQL table house_points (replace if it exists, no index). Then append one more house with if_exists='append' and read the table back sorted by Points.
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://root:tiger@localhost:3306/school')
hp = pd.DataFrame({'House': ['Shivaji', 'Tagore', 'Raman'],
'Captain': ['Arnav', 'Mehak', 'Yuvraj'],
'Points': [412, 389, 455]})
hp.to_sql('house_points', engine, if_exists='replace', index=False)
extra = pd.DataFrame({'House': ['Kalam'], 'Captain': ['Zara'], 'Points': [430]})
extra.to_sql('house_points', engine, if_exists='append', index=False)
print(pd.read_sql_query('SELECT * FROM house_points ORDER BY Points DESC', engine))House Captain Points 0 Raman Yuvraj 455 1 Kalam Zara 430 2 Shivaji Arnav 412 3 Tagore Mehak 389
Viva questions (3)
- What happens if
if_existsis left out and the table exists? ValueError is raised. - Why pass
index=False? So the DataFrame's row labels are not stored as an extra column. - Does
to_sql()create the database? No, only the table; the database must already exist.
Common mistakes
Writing
df.mean()on a DataFrame with a Name column. Pandas 2.0 and later raiseTypeError; writedf.mean(numeric_only=True)or select the numeric columns first.Treating
mode()as a single number. It returns a Series (there can be several modes); uses.mode()[0]when one value is needed.Confusing
count()withsize/len().count()skips NaN, so it can be smaller than the number of rows.Expecting
quantile(0.75)to be one of the data values. Pandas interpolates, so 86.75 is a valid answer for whole-number marks.Writing
sales.groupby('Category').sum()when the table has text columns. Select the column first:sales.groupby('Category')['Expenditure'].sum().Searching for missing values with
df['Marks'] == np.nan. NaN is never equal to anything; usedf['Marks'].isnull().Calling
df.sort_values('Total')ordf.fillna(0)and expectingdfto change. These return new DataFrames; assign the result back.Forgetting
index=Falseinto_sql(), which adds an unwantedindexcolumn to the MySQL table, or runningto_sql()twice with the defaultif_exists='fail'and gettingValueError.