📌 Snapshot
- Pandas II made easy: statistics, sorting, groupby, pivot tables, missing values and MySQL links explained.
- How max, min, sum, count, mean, median, mode, quantile, var and std behave on columns, rows and groups.
- Why pivot fails on duplicate entries, how pivot_table fixes it, and how dropna and fillna change your answers.
- The exact steps to move a table between MySQL and a DataFrame using pymysql, sqlalchemy, read_sql and to_sql.
📖 Detailed Notes
Written and reviewed by Rohit Singh, Computer Science & Commerce faculty at UniDrill (MCA, Osmania University · 6 years teaching). Based on NCERT Informatics Practices Class XII, Chapter 3.
Which student is improving? Five lines can tell you
Twelve rows of marks sit in front of you: four students, three unit tests, five subjects. Your eyes cannot say who improved, which test was hardest or who is the most consistent. Pandas can answer each of those in one line. This chapter is the toolkit for that: summarise the data, sort it, group it, reshape it, clean it, and move it in and out of MySQL.
Every example below uses one DataFrame called df, built from the unit test marks (maximum 25 in each subject).
| Code |
|---|
import pandas as pd |
marksUT = {'Name': ['Raman','Raman','Raman','Zuhaire','Zuhaire','Zuhaire', |
'Ashravy','Ashravy','Ashravy','Mishti','Mishti','Mishti'], |
'UT': [1,2,3,1,2,3,1,2,3,1,2,3], |
'Maths': [22,21,14,20,23,22,23,24,12,15,18,17], |
'Science': [21,20,19,17,15,18,19,22,25,22,21,18], |
'S.St': [18,17,15,22,21,19,20,24,19,25,25,20], |
'Hindi': [20,22,24,24,25,23,15,17,21,22,24,25], |
'Eng': [21,24,23,19,15,13,22,21,23,22,23,20]} |
df = pd.DataFrame(marksUT) |
The resulting DataFrame has row labels 0 to 11. The marks for each row, in the order Maths, Science, S.St, Hindi, Eng, are:
| Row | Name | UT | Marks |
|---|---|---|---|
| 0 | Raman | 1 | 22, 21, 18, 20, 21 |
| 1 | Raman | 2 | 21, 20, 17, 22, 24 |
| 2 | Raman | 3 | 14, 19, 15, 24, 23 |
| 3 | Zuhaire | 1 | 20, 17, 22, 24, 19 |
| 4 | Zuhaire | 2 | 23, 15, 21, 25, 15 |
| 5 | Zuhaire | 3 | 22, 18, 19, 23, 13 |
| 6 | Ashravy | 1 | 23, 19, 20, 15, 22 |
| 7 | Ashravy | 2 | 24, 22, 24, 17, 21 |
| 8 | Ashravy | 3 | 12, 25, 19, 21, 23 |
| 9 | Mishti | 1 | 15, 22, 25, 22, 22 |
| 10 | Mishti | 2 | 18, 21, 25, 24, 23 |
| 11 | Mishti | 3 | 17, 18, 20, 25, 20 |
Descriptive statistics: one function per question
Descriptive statistics squeeze many numbers into a few that describe them. Each function below is called on a whole DataFrame (one answer per column) or on a single column.
| Function | What it returns | Works on text columns? | Example on the Maths column |
|---|---|---|---|
max() |
Largest value | Yes (alphabetical for text) | 24 |
min() |
Smallest value | Yes | 12 |
sum() |
Total | Yes, but joins text end to end | 231 |
count() |
Number of non missing values | Yes | 12 |
mean() |
Average | Numbers only | 19.25 |
median() |
Middle value of the sorted data | Numbers only | 20.5 |
mode() |
Most frequent value, returned as a Series | Yes | Hindi: 24 |
quantile(q) |
Value below which a fraction q of the data lies (default 0.5) | Numbers only | q = 0.25 gives 16.5 |
var() |
Variance | Numbers only | 15.840909 |
std() |
Standard deviation, the square root of the variance | Numbers only | 3.980064 |
describe() |
count, mean, std, min, 25%, 50%, 75% and max in one shot | Numbers only | see below |
Rows or columns? The axis rule. By default (axis=0) a statistic runs down the rows and gives one answer per column. With axis=1 it runs across the columns and gives one answer per row.
| Call | Result |
|---|---|
df['Maths'].max() |
One number: the highest Maths mark |
df.max(numeric_only=True) |
One maximum for every numeric column |
df[['Maths','Science']].max(axis=1) |
One maximum for every row, taken across the two subjects |
Worked example: median of Maths in UT1. Write the statements to find the median Maths mark in Unit Test 1.
| Code |
|---|
dfMathsUT1 = df[df.UT == 1]['Maths'] |
print(dfMathsUT1.median()) |
| Step | What we do | Result |
|---|---|---|
| 1 | df.UT == 1 builds a True/False filter and selects the UT1 rows |
Rows 0, 3, 6, 9 |
| 2 | ['Maths'] keeps only that column |
22, 20, 23, 15 |
| 3 | Sort the values | 15, 20, 22, 23 |
| 4 | Four values means two middle ones (20 and 22), so the median is their average | 21.0 |
| Output |
|---|
21.0 |
The common wrong step is to take the 2nd or 3rd value of the unsorted list. Median always works on sorted data.
Worked example: totals for one student. Find Raman's total marks in each subject, and in each unit test.
| Code |
|---|
dfRaman = df[df['Name'] == 'Raman'] |
subjects = ['Maths','Science','S.St','Hindi','Eng'] |
print(dfRaman[subjects].sum()) |
print(dfRaman[subjects].sum(axis=1)) |
The first sum() runs down the three rows and gives one total per subject. The second, with axis=1, runs across the five subjects and gives one total per unit test.
| Subject | Value |
|---|---|
| Maths | 57 |
| Science | 60 |
| S.St | 50 |
| Hindi | 66 |
| Eng | 68 |
| UT row | Value |
|---|---|
| 0 | 102 |
| 1 | 104 |
| 2 | 95 |
Pause and predict: df['Hindi'].mode() is run on the full data. Three rows show 24, two rows show 25, and every other mark appears once or twice. What does it return?
Check your answer: A Series with index 0 and the value 24. mode() always returns a Series, because when two values tie for most frequent, both are returned. Here 24 appears three times, which beats every other mark, so there is a single row.
Worked example: variance by hand. Mishti's Maths marks are 15, 18 and 17. Find the variance.
Variance is the average of the squared distances from the mean. Pandas divides by $n-1$, not $n$:
$$s^2 = \dfrac{\sum (x_i - \bar{x})^2}{n-1}$$
| Step | What we do | Result |
|---|---|---|
| 1 | Mean: $(15+18+17)/3$ | $\bar{x} = 16.667$ |
| 2 | Squared distances | $2.778,\ 1.778,\ 0.111$ |
| 3 | Add them | $4.667$ |
| 4 | Divide by $n-1 = 2$ | $s^2 = 2.333$ |
| 5 | Square root gives std() |
$1.5275$ |
The common wrong step is dividing by 3, which gives 1.556 and matches none of the options. The same figure, 2.333333, appears for Mishti in the group results later in this chapter.
Beyond the textbook. For the exam: df.max(), df.sum() and df.min() work on the whole DataFrame, including the Name column, and df.mean() is used on numeric columns. In reality: recent versions of Pandas raise a TypeError if you call mean(), median() or std() on a DataFrame that still contains a text column, so you write df.mean(numeric_only=True) or select the numeric columns first. Using numeric_only=True is safe in every version.
describe() produces the whole summary at once. For three subject columns:
| Code |
|---|
print(df[['Maths','Science','Hindi']].describe()) |
| Statistic | Maths | Science | Hindi |
|---|---|---|---|
| count | 12.000000 | 12.00000 | 12.000000 |
| mean | 19.250000 | 19.75000 | 21.833333 |
| std | 3.980064 | 2.66714 | 3.157483 |
| min | 12.000000 | 15.00000 | 15.000000 |
| 25% | 16.500000 | 18.00000 | 20.750000 |
| 50% | 20.500000 | 19.50000 | 22.500000 |
| 75% | 22.250000 | 21.25000 | 24.000000 |
| max | 24.000000 | 25.00000 | 25.000000 |
The 25%, 50% and 75% rows are the first, second and third quartiles, which are the same values quantile(.25), quantile(.5) (the median) and quantile(.75) return. You can ask for several at once with df.quantile([.25, .75]).
Aggregation: many values in, one value out
Aggregation turns an array of values into a single number. The aggregate functions are max, min, sum, count, std and var. The method aggregate() (short form agg()) lets you name them as strings and ask for several at once.
| Code |
|---|
print(df[['Maths','Science']].aggregate(['max','min'])) |
print(df[['Maths','Science']].aggregate('sum', axis=1).head(3)) |
| Statistic | Maths | Science |
|---|---|---|
| max | 24 | 25 |
| min | 12 | 15 |
With axis=1, aggregate('sum', axis=1) adds Maths and Science for every row. The first three rows give 43, 41 and 33.
Sorting a DataFrame
sort_values() rearranges the rows by the values in one or more columns. The syntax is DataFrame.sort_values(by, axis=0, ascending=True). The row labels travel with their rows, so after sorting the index is no longer 0, 1, 2 in order.
Worked example: sort with a tie-break. For Unit Test 3, sort the students by Science (low to high), and break ties using Hindi.
| Code |
|---|
dfUT3 = df[df.UT == 3] |
print(dfUT3[['Name','Science','Hindi']].sort_values(by=['Science','Hindi'])) |
| Row label | Name | Science | Hindi |
|---|---|---|---|
| 5 | Zuhaire | 18 | 23 |
| 11 | Mishti | 18 | 25 |
| 2 | Raman | 19 | 24 |
| 8 | Ashravy | 25 | 21 |
Zuhaire and Mishti both have 18 in Science. Python moves on to the second column in the list, Hindi, where 23 is smaller than 25, so Zuhaire comes first. For a descending sort add ascending=False, for example dfUT3.sort_values(by=['Eng'], ascending=False).
Pause and predict: In the output above, what is the row label of the last student, and why is it not 3?
Check your answer: It is 8 (Ashravy). Sorting moves whole rows, so each row keeps the label it had in df. Use reset_index(drop=True) if you want fresh labels.
groupby: split, apply, combine
groupby() is the Pandas version of SQL's GROUP BY. It works in three steps.
| Step | What happens | In the unit test example |
|---|---|---|
| Split | Rows are divided into groups by the key column(s) | One group per student |
| Apply | A function (sum, mean, max…) runs on each group | Average the Maths marks |
| Combine | The group answers are joined into a new DataFrame or Series | One row per student |
| Code |
|---|
g1 = df.groupby('Name') |
print(g1.size()) |
print(g1.get_group('Raman')[['UT','Maths']]) |
| Name | Value |
|---|---|
| Ashravy | 3 |
| Mishti | 3 |
| Raman | 3 |
| Zuhaire | 3 |
| Row label | UT | Maths |
|---|---|---|
| 0 | 1 | 22 |
| 1 | 2 | 21 |
| 2 | 3 | 14 |
Useful members of a groupby object are first() (first row of each group), size() (rows per group), groups (a dictionary of group name and its row labels) and get_group(name) (all the rows of one group). Group by more than one column with a list: df.groupby(['Name','UT']).
Worked example: average Maths in each unit test. Which test was the hardest for Maths?
| Code |
|---|
print(df.groupby('UT')['Maths'].aggregate('mean')) |
| UT | Maths |
|---|---|
| 1 | 20.00 |
| 2 | 21.50 |
| 3 | 16.25 |
UT3 has the lowest average, 16.25. You can check it by hand: the UT3 marks are 14, 22, 12 and 17, which add to 65, and $65/4 = 16.25$.
To get several statistics per group, pass a list. This is the statement for the mean, variance, standard deviation and median of Maths for each student:
| Code |
|---|
res = df.groupby('Name')['Maths'].agg(['mean','var','std','median']) |
print(res) |
| Name | mean | var | std | median |
|---|---|---|---|---|
| Ashravy | 19.666667 | 44.333333 | 6.658328 | 23.0 |
| Mishti | 16.666667 | 2.333333 | 1.527525 | 17.0 |
| Raman | 19.000000 | 19.000000 | 4.358899 | 21.0 |
| Zuhaire | 21.666667 | 2.333333 | 1.527525 | 22.0 |
Ashravy's variance (44.33) is by far the largest because the Maths marks swing between 24 and 12, while Mishti's and Zuhaire's are steady.
Beyond the textbook. For the exam: learn the three steps, split, apply, combine, and the members first, size, groups and get_group. In reality: df.groupby('Name')['Maths'].mean() does the same job as the SQL query SELECT AVG(Maths) FROM marks GROUP BY Name. The idea is identical, only the syntax differs, so what you learn in Querying and SQL Functions carries over. Note the spelling: in Python it is groupby, one lowercase word.
Altering the index
By default the row labels are 0, 1, 2… When you filter a DataFrame, the labels of the surviving rows stay as they were, so they are no longer continuous.
| Code |
|---|
dfUT1 = df[df.UT == 1][['Name','Maths']] |
print(dfUT1) |
| Row label | Name | Maths |
|---|---|---|
| 0 | Raman | 22 |
| 3 | Zuhaire | 20 |
| 6 | Ashravy | 23 |
| 9 | Mishti | 15 |
The labels are 0, 3, 6 and 9. These functions fix or change the index:
| Function | What it does |
|---|---|
reset_index() |
Creates a fresh 0, 1, 2… index and keeps the old labels as a new column called index |
reset_index(drop=True) |
Creates the fresh index and throws the old labels away |
set_index('Name') |
Makes the Name column the row labels (the column leaves the data columns) |
drop(columns=['index']) |
Removes a column, used to delete the leftover index column |
| Code |
|---|
dfNew = dfUT1.reset_index(drop=True) |
dfNew = dfNew.set_index('Name') |
print(dfNew) |
| Name | Maths |
|---|---|
| Raman | 22 |
| Zuhaire | 20 |
| Ashravy | 23 |
| Mishti | 15 |
Reversing it with reset_index() moves Name back into a normal column and restores 0, 1, 2, 3.
Reshaping data: pivot and pivot_table
The shape of data is how it is laid out in rows and columns. A long list of (store, year, sales) rows is hard to compare. Reshaping turns it into a grid with one row per store and one column per year.
| Code |
|---|
data = {'Store': ['S1','S4','S3','S1','S2','S3','S1','S2','S3'], |
'Year': [2016,2016,2016,2017,2017,2017,2018,2018,2018], |
'Total_sales(Rs)': [12000,330000,420000,20000,10000,450000,30000,11000,89000]} |
st = pd.DataFrame(data) |
pivot1 = st.pivot(index='Store', columns='Year', values='Total_sales(Rs)') |
print(pivot1) |
| Store / Year | 2016 | 2017 | 2018 |
|---|---|---|---|
| S1 | 12000.0 | 20000.0 | 30000.0 |
| S2 | NaN | 10000.0 | 11000.0 |
| S3 | 420000.0 | 450000.0 | 89000.0 |
| S4 | 330000.0 | NaN | NaN |
index names the column that becomes the row labels, columns the column whose values become the new headings, and values the column that fills the grid. Combinations that never occurred, such as S2 in 2016, appear as NaN. Questions are now one-liners: pivot1.loc['S1'].sum() gives 62000.0 for store S1 over all years, and pivot1.loc['S3'].max() gives 450000.0.
Worked example: why does pivot raise an error? In a stock table every pen colour appears twice:
| Code |
|---|
stock = pd.DataFrame({'Item': ['Pen','Pen','Pencil','Pencil','Pen','Pen'], |
'Color': ['Red','Red','Black','Black','Blue','Blue'], |
'Price(Rs)': [10,25,7,5,50,20], |
'Units_in_stock': [50,10,47,34,55,14]}) |
stock.pivot(index='Item', columns='Color', values='Units_in_stock') |
| Step | What we do | Result |
|---|---|---|
| 1 | Pivot tries to put each (Item, Color) pair in one cell | Pen and Red must fill one cell |
| 2 | But the pair Pen and Red occurs twice (stock 50 and 10) | Two values, one cell |
| 3 | Pivot cannot choose, so it stops | ValueError: Index contains duplicate entries, cannot reshape |
pivot_table() solves this by aggregating the duplicates. Its syntax is pivot_table(data, values=None, index=None, columns=None, aggfunc='mean'), and the default aggregate is the mean.
| Code |
|---|
print(stock.pivot_table(index=['Item','Color'])) |
| Item / Color | Price(Rs) | Units_in_stock |
|---|---|---|
| Pen / Blue | 35.0 | 34.5 |
| Pen / Red | 17.5 | 30.0 |
| Pencil / Black | 6.0 | 40.5 |
The blue pen was priced at 50 and 20, so the table shows the mean, 35.0. You can choose another aggregate, such as aggfunc='sum', or several at once with a list like aggfunc=['sum','max','mean'], or a different function per column with a dictionary like aggfunc={'Price(Rs)': len, 'Units_in_stock': 'mean'}. The function len counts the rows behind each cell.
| Feature | pivot() | pivot_table() |
|---|---|---|
| Duplicate entries for a cell | Raises ValueError | Aggregates them |
| Default aggregate | None | mean |
| Use it when | Every index and column pair is unique | Pairs repeat, or you want a summary |
| Missing combinations | NaN | NaN |
Pause and predict: You call stock.pivot_table(index='Item', columns='Color', values='Units_in_stock'). What value appears for Pen and Blue?
Check your answer: 34.5, the mean of 55 and 14, because the default aggregate is the mean. Pencil and Blue is NaN, because no such row exists.
Handling missing values
A missing value is shown as NaN. It appears when data was not collected or the attribute does not apply. Missing values distort results, and there are two main strategies: drop the rows that have them, or fill (estimate) them.
Suppose a fourth test happened and Raman missed Maths, Science and English, and no retest is possible. The DataFrame below holds Raman's four rows, and only the UT4 row has NaN.
| Code |
|---|
import numpy as np |
marks4 = pd.DataFrame({'Name': ['Raman']*4, |
'UT': [1,2,3,4], |
'Maths': [22,21,14,np.nan], |
'Science': [21,20,19,np.nan], |
'Hindi': [20,22,24,18], |
'Eng': [21,24,23,np.nan]}) |
print(marks4.isnull().sum()) |
print(marks4.isnull().sum().sum()) |
| Column | Value |
|---|---|
| Name | 0 |
| UT | 0 |
| Maths | 1 |
| Science | 1 |
| Hindi | 0 |
| Eng | 1 |
isnull() returns a True/False DataFrame, any() tells whether a column has at least one True, and sum() counts the Trues, since True counts as 1. The second line of the code adds the column counts and prints the total number of missing cells, 3.
| Check | Code | Answer here |
|---|---|---|
| Is anything missing in each column? | marks4.isnull().any() |
True for Maths, Science and Eng |
| How many missing in each column? | marks4.isnull().sum() |
1, 1 and 1 |
| How many missing in the whole table? | marks4.isnull().sum().sum() |
3 |
Worked example: one student, three missing-value policies. Each teacher converts Raman's marks to a percentage out of the total marks of the tests held. The subjects have maximum 25 marks per test.
| Teacher | Policy | Code idea | Calculation | Percentage |
|---|---|---|---|---|
| Maths | Drop UT4 for Raman | dropna() |
$\dfrac{57}{25 \times 3} \times 100$ | 76.0 |
| Science | Treat the missing mark as zero | fillna(0) |
$\dfrac{60}{25 \times 4} \times 100$ | 60.0 |
| English | Repeat the previous test's mark | fillna(method='pad') |
$\dfrac{21+24+23+23}{25 \times 4} \times 100$ | 91.0 |
| Code |
|---|
r = marks4 |
print(r.dropna()['Maths'].sum() * 100 / (25 * len(r.dropna()))) |
print(r['Science'].fillna(0).sum() * 100 / (25 * len(r))) |
print(r['Eng'].fillna(method='pad').sum() * 100 / (25 * len(r))) |
| Output |
|---|
76.0 |
60.0 |
91.0 |
Same student, three different answers. Dropping reduces the number of rows, so the denominator falls from 100 to 75. Filling keeps four rows.
Common wrong step. If you do nothing and compute r['Maths'].sum() * 100 / (25 * 4), Pandas quietly skips the NaN while adding, and gives 57.0. That looks like a normal answer but it is the same as treating the missing test as zero. Missing data never raises an error, it just changes the result.
| Function | What it does | Note |
|---|---|---|
dropna() |
Drops every row that has at least one NaN | The whole row goes, including the good values |
fillna(value) |
Replaces NaN with the given value | fillna(0) and fillna(1) are common |
fillna(method='pad') |
Fills with the value before the gap | Forward fill |
fillna(method='bfill') |
Fills with the value after the gap | Backward fill |
Beyond the textbook. For the exam: write fillna(method='pad') and fillna(method='bfill'). In reality: newer Pandas versions deprecate the method= argument, and the same results come from ffill() (forward fill) and bfill(). Whichever you write, the idea is unchanged: any filled value is an estimate, so the results are close approximations and not the actual figures.
Import and export between Pandas and MySQL
Real data lives in files and databases, not in dictionaries you type. Pandas can read a MySQL table into a DataFrame (import) and write a DataFrame into a MySQL table (export). Two libraries make the connection possible.
| Library | Role | Install with |
|---|---|---|
pymysql |
The driver that speaks to MySQL | pip install pymysql |
sqlalchemy |
Provides create_engine(), which opens the connection |
pip install sqlalchemy |
create_engine() takes a connection string. Its parts are the driver, the user name, the password, the host, the port and the database name:
| Part | Example |
|---|---|
| Driver | mysql+pymysql |
| User name | root |
| Password | your MySQL password |
| Host and port | localhost:3306 (3306 is the default port) |
| Database | CARSHOWROOM |
Importing. Three functions read a table into a DataFrame, each taking the connection (engine) as its second argument:
| Function | Reads |
|---|---|
pandas.read_sql_query(query, sql_conn) |
The result of an SQL query |
pandas.read_sql_table(table_name, sql_conn) |
A whole table |
pandas.read_sql(sql, sql_conn) |
Either a query or a table |
| Code |
|---|
import pandas as pd |
import pymysql |
from sqlalchemy import create_engine |
engine = create_engine('mysql+pymysql://root:yourpassword@localhost:3306/CARSHOWROOM') |
df = pd.read_sql_query("SELECT CarId, CarName FROM INVENTORY WHERE Fueltype = 'CNG'", engine) |
print(df) |
If the INVENTORY table holds two CNG cars, E001 and E002, both of Car3, the output is:
| Index | CarId | CarName |
|---|---|---|
| 0 | E001 | Car3 |
| 1 | E002 | Car3 |
Exporting. DataFrame.to_sql(table, sql_conn, if_exists='fail', index=True) writes a DataFrame into a table. The argument if_exists decides what happens when the table already exists.
| if_exists | Behaviour |
|---|---|
'fail' (default) |
Raises ValueError if the table already exists |
'replace' |
Replaces the old table contents with the DataFrame |
'append' |
Adds the rows to the existing table (column order must match) |
| Code |
|---|
data = {'ShowRoomId': [1,2,3,4,5], |
'Location': ['Delhi','Bangalore','Mumbai','Chandigarh','Kerala']} |
df = pd.DataFrame(data) |
df.to_sql('showroom_info', engine, if_exists='replace', index=False) |
This creates the table showroom_info in the database. With index=True (the default) the DataFrame's row labels are also written as an extra column, so pass index=False when you only want the data.
Pause and predict: You run df.to_sql('showroom_info', engine) twice, with the same engine. What happens on the second run?
Check your answer: It raises a ValueError. The default is if_exists='fail', and the table was created by the first run. Pass 'append' to add rows or 'replace' to overwrite.
Exam traps
| Trap | Why it is tempting | Truth |
|---|---|---|
groupby is written GROUP BY or GroupBy |
SQL uses GROUP BY | The Python method is groupby, one lowercase word |
axis=0 returns row results |
"0" sounds like rows | axis=0 (default) gives one answer per column, and axis=1 one answer per row |
var() divides by n |
The school formula for a population does | Pandas divides by $n-1$, so the answer is larger |
mode() returns a number |
mean() and median() do |
It returns a Series, and more than one value on a tie |
pivot() and pivot_table() are the same |
Both reshape | pivot fails on duplicates, pivot_table aggregates them (default mean) |
sum() of a column with NaN fails or gives NaN |
NaN usually spreads | NaN is skipped, so the result silently ignores missing data |
dropna() drops only the missing cell |
The cell is the problem | It drops the whole row |
to_sql() appends by default |
You usually want to add rows | The default is fail, which errors if the table exists |
60-second revision
| Task | Code | Remember |
|---|---|---|
| Summary of numeric columns | df.describe() |
25%, 50%, 75% are the quartiles |
| Row-wise answer | add axis=1 |
Default axis=0 is column-wise |
| Median, mode, variance | median(), mode(), var() |
mode is a Series, var uses $n-1$ |
| Several statistics together | agg(['max','min']) |
Strings in a list |
| Sort | sort_values(by=['A','B'], ascending=False) |
Row labels move with rows |
| Group and summarise | groupby('key')['col'].mean() |
Split, apply, combine |
| Fresh index | reset_index(drop=True) |
Without drop=True the old labels become a column |
| Row labels from a column | set_index('Name') |
|
| Reshape | pivot() or pivot_table() |
Duplicates need pivot_table |
| Find missing | isnull().sum() |
Total: .sum().sum() |
| Handle missing | dropna(), fillna(0), ffill() |
Filling is an estimate |
| Read from MySQL | read_sql_query(query, engine) |
Needs pymysql and sqlalchemy |
| Write to MySQL | to_sql('t', engine, if_exists='replace', index=False) |
Default if_exists is 'fail' |
Where this chapter connects
| Chapter | Link |
|---|---|
| The Series and DataFrame basics used here | Data Handling using Pandas I |
| Drawing charts from these results | Plotting Data using Matplotlib |
| The SQL behind GROUP BY and the MySQL tables | Querying and SQL Functions |
| Where the data comes from and how to read it | Understanding Data |
🎯 Practice MCQs
First 3 questions free · create a free account to unlock the rest — answers & explanations included, no payment needed
Q1. For a DataFrame df, the statement df.max(numeric_only=True) with no axis argument returns
▸ Show answer & explanation
Answer: D
The default axis is 0, so the function runs down the rows and gives one answer per column. One answer per row needs axis=1. A single overall number would need a further max() on the result. Pandas never returns the whole row from max().
Q2. Consider: (1) import pandas as pd (2) s = pd.Series([4, 8, 8, 10, 15]) (3) print(s.median(), s.mode()[0]). What is printed?
▸ Show answer & explanation
Answer: A
The five sorted values have 8 in the middle, so the median is 8.0 (a float). The value 8 occurs twice and is the mode, and mode() returns a Series whose first element is 8. The mean would be 9.0, which is why options C and D are tempting but wrong.
Q3. What does pd.Series([2, 4, 6]).var() return?
▸ Show answer & explanation
Answer: C
The mean is 4, and the squared distances 4, 0 and 4 add up to 8. Pandas divides by n minus 1, which is 2, giving 4.0. Dividing by n gives 2.6667 (option A), 2.0 is the standard deviation (the square root of 4.0), and 8.0 is the unscaled sum.
🔒 12 more practice MCQs
Create a free account to unlock every MCQ in this chapter — answers and explanations included. No payment needed.
Already registered? Just log in and they'll all appear here.
Q4. For the data 1, 2, 3, 4, 5 stored in a Series s, what is s.quantile(0.75)?
▸ Show answer & explanation
Answer: A
The third quartile is the value below which 75 percent of the data lies. The position is 0.75 times 4, which is 3, counting from zero, so the value is the fourth element, 4.0. Option D is the median (quantile 0.5) and option B is the maximum.
Q5. Which of the following statements about sort_values() are correct? (i) It sorts in ascending order by default. (ii) A list of column names can be passed to by, and later columns break ties. (iii) After sorting, the row labels are renumbered from 0. (iv) ascending=False gives descending order.
▸ Show answer & explanation
Answer: B
Statements (i), (ii) and (iv) are correct. Statement (iii) is wrong because each row keeps its original label when sorted. The labels are renumbered only if you call reset_index().
Q6. In the groupby mechanism of Pandas, the correct order of the steps is
▸ Show answer & explanation
Answer: B
The data is first split into groups by a key, then a function is applied to each group, and finally the results are combined into a new object. The other orders cannot work because a function cannot be applied before groups exist, and results cannot be combined before they are computed.
Q7. Consider: (1) import pandas as pd (2) d = pd.DataFrame({'k': ['a','b','a','b'], 'v': [1,2,3,4]}) (3) print(d.groupby('k')['v'].sum()). Which result is printed for the groups a and b?
▸ Show answer & explanation
Answer: C
Group a contains the v values 1 and 3, which add to 4. Group b contains 2 and 4, which add to 6. Option D adds neighbouring rows, option B swaps the groups, and option A is the total of all values given to each group.
Q8. A DataFrame has two rows with the same Item and Color. The call df.pivot(index='Item', columns='Color', values='Stock') raises ValueError. Which function avoids the error?
▸ Show answer & explanation
Answer: C
pivot() cannot place two values in one cell, so it raises ValueError for duplicate entries. pivot_table() aggregates the duplicates (by default with the mean) and fills the cell. sort_values() and reset_index() do not change the duplicates, and groupby().first() does not reshape the table.
Q9. Which statement gives the total number of missing values in the whole DataFrame df?
▸ Show answer & explanation
Answer: A
df.isnull() gives True for each missing cell, the first sum() counts them per column and the second sum() adds those counts into one number. Option D stops after the first sum and so gives a count for each column. Option B only says whether each column has any missing value, and option C counts the values that remain after the rows with missing values are removed.
Q10. Consider: (1) import pandas as pd (2) import numpy as np (3) s = pd.Series([10, np.nan, 30]) (4) print(s.sum(), s.count()). What is printed?
▸ Show answer & explanation
Answer: B
sum() skips NaN, so it adds 10 and 30 to get 40.0 (a float, because the Series holds floats). count() counts only the non missing values, so it is 2. Option D is wrong since NaN does not spread through the sum, option A counts the NaN as a value, and option C shows an integer sum, which is not possible for a float Series.
Q11. Which statement about dropna() is correct?
▸ Show answer & explanation
Answer: D
By default dropna() removes the entire row that has a missing value, including its good values. It does not shift cells (option C), it does not replace anything (option B, that is fillna), and it works on rows by default, not columns (option A).
Q12. A Series is s = pd.Series([5, np.nan, np.nan, 9]). After the missing values are replaced by the previous valid value (pad), what values does the Series hold?
▸ Show answer & explanation
Answer: A
Pad (forward fill) copies the value before each gap forward, so both NaN cells become 5. Option C is backward fill (bfill), option D is fillna(0), and option B is an average that no fill method produces here.
Q13. Match List I with List II. List I: (i) set_index() (ii) reset_index() (iii) isnull() (iv) quantile(). List II: (p) tests each cell for a missing value (q) makes a column the row labels (r) restores the default 0, 1, 2 labels (s) returns the value below which a given fraction of the data falls.
▸ Show answer & explanation
Answer: D
set_index() turns a column into row labels, reset_index() brings back the default numbered labels, isnull() flags missing cells and quantile() gives a position in the sorted data. The other options mismatch at least two pairs.
Q14. Assertion (A): pivot_table() can summarise a table in which the same combination of index and column values occurs more than once. Reason (R): pivot_table() always uses the sum as its default aggregate function.
▸ Show answer & explanation
Answer: C
pivot_table() aggregates the duplicate entries, so A is true. Its default aggregate function is the mean and not the sum, so R is false.
Q15. Which statement correctly creates the engine for a MySQL database named school on the local machine with the default port, user root and password abc123?
▸ Show answer & explanation
Answer: C
The connection string follows the pattern driver://username:password@host:port/database, with the driver written as mysql+pymysql. The other options put the user, password and database name in the wrong places or omit the driver.
📊 Previous-Year Questions
Practise with real CUET Computer Science previous-year papers — every question solved, with the correct answer and a step-by-step explanation.
View solved CUET PYQ papers →Ready to drill Computer Science?
Unlock all MCQs, chapter tests, mocks & PYQs for ₹199/year.
Get UniDrill Pro