From Zero to Pandas (Part 4): Sorting Through Data
Cut Through Messy Data and Keep What Matters

9. Selecting Rows and Columns in Pandas
In the previous section, you learned how to inspect a DataFrame and understand the information it contains.
Now imagine you have a dataset with hundreds or thousands of records. You probably do not want to work with every row and every column at the same time.
You may want to answer questions such as:
- What are the names of all the students?
- What scores did students in SS1 receive?
- Which students scored above 80?
- What information is recorded for the student in the third row?
- Which students scored below 50?
Pandas allows you to select specific columns and rows without manually searching through the entire dataset.
In this section, you will learn how to select columns, select rows using their positions or labels, filter records using conditions, and combine multiple conditions.
9.1 Create a Dataset to Work With
Let's create a dataset containing student examination results from a school in Abuja.
import pandas as pd
students = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David", "Lydia", "Samuel"],
"Class": ["SS1", "SS1", "SS2", "SS2", "SS1", "SS2"],
"Age": [15, 16, 17, 16, 15, 17],
"Score": [85, 72, 91, 45, 88, 67]
})
print(students)
Output:
Name Class Age Score
0 Amina SS1 15 85
1 Brian SS1 16 72
2 Grace SS2 17 91
3 David SS2 16 45
4 Lydia SS1 15 88
5 Samuel SS2 17 67
We will use this DataFrame for the examples in this section.
9.2 Select One Column
To select one column, use its name inside square brackets.
For example, to display only the students' names:
print(students["Name"])
Output:
0 Amina
1 Brian
2 Grace
3 David
4 Lydia
5 Samuel
Name: Name, dtype: object
This returns a pandas Series.
A Series is a one-dimensional structure containing a single column of data.
You can also select the scores:
print(students["Score"])
Output:
0 85
1 72
2 91
3 45
4 88
5 67
Name: Score, dtype: int64
This is useful when you want to calculate statistics or work with one particular column.
For example:
print(students["Score"].mean())
Output:
74.66666666666667
9.3 Select Multiple Columns
What if you want to display both the students' names and their scores?
You can select multiple columns by passing a list of column names inside the square brackets.
print(students[["Name", "Score"]])
Output:
Name Score
0 Amina 85
1 Brian 72
2 Grace 91
3 David 45
4 Lydia 88
5 Samuel 67
Notice the double square brackets:
- The outer brackets tell pandas that you are selecting from the DataFrame.
- The inner brackets contain the list of column names.
You can select as many columns as you need:
print(students[["Name", "Class", "Score"]])
This returns a DataFrame containing only those three columns.
The order of the column names determines their order in the result.
9.4 Select Rows Using iloc
Sometimes you want to select rows based on their position.
Pandas provides iloc for position-based selection.
For example, to select the first row:
print(students.iloc[0])
Output:
Name Amina
Class SS1
Age 15
Score 85
Name: 0, dtype: object
Remember that Python uses zero-based indexing:
- Position 0 is the first row.
- Position 1 is the second row.
- Position 2 is the third row.
To select the third row:
print(students.iloc[2])
Output:
Name Grace
Class SS2
Age 17
Score 91
Name: 2, dtype: object
You can also select several rows using slicing.
For example:
print(students.iloc[0:3])
Output:
Name Class Age Score
0 Amina SS1 15 85
1 Brian SS1 16 72
2 Grace SS2 17 91
This selects rows at positions 0, 1, and 2.
The ending position, 3, is not included.
9.5 Select Specific Rows and Columns with iloc
You can use iloc to select both rows and columns.
The general structure is:
dataframe.iloc[row_selection, column_selection]
For example:
print(students.iloc[0:3, 0:2])
Output:
Name Class
0 Amina SS1
1 Brian SS1
2 Grace SS2
Let's understand the code:
0:3selects the first three rows.0:2selects the first two columns.
You can also select one particular value:
print(students.iloc[0, 3])
Output:
85
This selects the value in the first row and fourth column.
9.6 Select Rows Using loc
While iloc selects rows by their position, loc selects rows using their index labels.
For example:
print(students.loc[0])
Output:
Name Amina
Class SS1
Age 15
Score 85
Name: 0, dtype: object
In this DataFrame, the index labels are 0, 1, 2, 3, 4, and 5.
Because the default index labels match the row positions, loc[0] and iloc[0] return the same row.
However, they are not always interchangeable.
Consider a DataFrame with custom index labels:
students_custom = students.set_index("Name")
print(students_custom)
Output:
Class Age Score
Name
Amina SS1 15 85
Brian SS1 16 72
Grace SS2 17 91
David SS2 16 45
Lydia SS1 15 88
Samuel SS2 17 67
Now you can select Grace's record using the index label:
print(students_custom.loc["Grace"])
Output:
Class SS2
Age 17
Score 91
Name: Grace, dtype: object
But you can still select the third row by position:
print(students_custom.iloc[2])
Both return Grace's record in this example, but they use different selection methods.
Remember:
locuses index labels.ilocuses integer positions.
9.7 Select Rows Using a Condition
One of the most useful pandas operations is filtering rows based on a condition.
Suppose you want to find all students who scored more than 80.
You can write:
high_scores = students[students["Score"] > 80]
print(high_scores)
Output:
Name Class Age Score
0 Amina SS1 15 85
2 Grace SS2 17 91
4 Lydia SS1 15 88
Let's understand what happened.
First:
students["Score"] > 80
This checks whether each student's score is greater than 80.
Pandas produces a series of True and False values.
It then uses those values to select only the rows where the condition is True.
You can use different comparison operators:
| Operator | Meaning |
|---|---|
> |
Greater than |
< |
Less than |
>= |
Greater than or equal to |
<= |
Less than or equal to |
== |
Equal to |
!= |
Not equal to |
For example, find students who scored below 50:
print(students[students["Score"] < 50])
Output:
Name Class Age Score
3 David SS2 16 45
Or find students who are exactly 17 years old:
print(students[students["Age"] == 17])
Output:
Name Class Age Score
2 Grace SS2 17 91
5 Samuel SS2 17 67
9.8 Filter Using Multiple Conditions
Sometimes one condition is not enough.
Imagine you want to find students who:
- Are in SS1.
- Scored more than 80.
You can combine the conditions using &, which means AND.
result = students[
(students["Class"] == "SS1") &
(students["Score"] > 80)
]
print(result)
Output:
Name Class Age Score
0 Amina SS1 15 85
4 Lydia SS1 15 88
Both conditions must be True for a row to be selected.
You can also use |, which means OR.
For example, find students who are in SS1 or scored above 80:
result = students[
(students["Class"] == "SS1") |
(students["Score"] > 80)
]
print(result)
Output:
Name Class Age Score
0 Amina SS1 15 85
1 Brian SS1 16 72
2 Grace SS2 17 91
4 Lydia SS1 15 88
A row is included if at least one of the conditions is True.
Important: Put each condition inside parentheses when combining conditions with & or |. Also, use & and | rather than Python's and and or for element-by-element pandas filtering.
9.9 Filter Using isin()
Suppose you want to select students from SS1 and SS2.
You could write two conditions, but pandas provides a convenient method called isin().
result = students[
students["Class"].isin(["SS1", "SS2"])
]
print(result)
Since every student in our dataset belongs to either SS1 or SS2, all six rows will appear.
isin() checks whether each value belongs to a list of allowed values.
It becomes more useful when you have many categories.
For example, if a larger dataset contains SS1, SS2, SS3, and other classes, you can select only SS1 and SS2 without writing several conditions.
9.10 Select Columns and Filter Rows Together
You can combine column selection and row filtering.
Suppose you want to see only the names and scores of students who scored above 80.
result = students.loc[
students["Score"] > 80,
["Name", "Score"]
]
print(result)
Output:
Name Score
0 Amina 85
2 Grace 91
4 Lydia 88
This example uses loc to specify:
- The rows that meet the condition.
- The columns you want to display.
It is a useful way to create a smaller, more focused view of a large dataset.
9.11 A Complete Practical Example
Let's put the ideas together.
Imagine a school administrator wants to identify students who need academic support.
The administrator wants to find students who scored below 50 and display only their names, classes, and scores.
import pandas as pd
students = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David", "Lydia", "Samuel"],
"Class": ["SS1", "SS1", "SS2", "SS2", "SS1", "SS2"],
"Age": [15, 16, 17, 16, 15, 17],
"Score": [85, 72, 91, 45, 88, 67]
})
# Find students who scored below 50
students_needing_support = students.loc[
students["Score"] < 50,
["Name", "Class", "Score"]
]
print(students_needing_support)
Output:
Name Class Score
3 David SS2 45
The administrator can now see that David scored below 50 and may need additional academic support.
Notice how a few lines of pandas code can answer a practical question quickly.
9.12 What You Should Remember
Here are the main selection techniques covered in this section:
| Technique | Purpose |
|---|---|
df["Column"] |
Select one column |
df[["A", "B"]] |
Select multiple columns |
df.iloc[0] |
Select a row by position |
df.loc[0] |
Select a row by index label |
df.iloc[0:3] |
Select rows by position range |
df[df["Score"] > 80] |
Filter rows using a condition |
& |
Combine conditions using AND |
| |
Combine conditions using OR |
isin() |
Select values from a list |
df.loc[condition, columns] |
Filter rows and select columns together |
The main lesson is that you do not always need to work with an entire DataFrame. You can select exactly the information needed to answer a particular question.
In the next section, we will learn how to clean data in pandas, including how to handle missing values, remove duplicates, and correct common data problems.
10. Cleaning Data in Pandas
In the previous section, you learned how to select rows and columns and filter data based on conditions.
But real-world data is not always clean.
Imagine receiving a spreadsheet containing customer records for a shop in Lagos. Some customers have missing phone numbers, a few records appear more than once, and some product prices have been entered incorrectly.
If you analyse this data without checking it, your results may be misleading.
This is why data cleaning is an important part of data analysis.
Data cleaning is the process of identifying and correcting problems in a dataset so that the information is more accurate, consistent, and useful.
In this section, you will learn how to:
- Identify missing values.
- Remove or fill missing values.
- Find and remove duplicate records.
- Correct inconsistent text.
- Rename columns.
- Convert data types.
- Identify unusual values.
10.1 Create a Dataset with Data Problems
Let's create a small customer dataset containing some common problems.
import pandas as pd
customers = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David", "Amina", None],
"City": ["Lagos", "abuja", "LAGOS", "Kano", "Lagos", "Abuja"],
"Age": [25, 30, None, 28, 25, 35],
"Amount": [50000, 75000, 60000, None, 50000, 90000]
})
print(customers)
Output:
Name City Age Amount
0 Amina Lagos 25.0 50000.0
1 Brian abuja 30.0 75000.0
2 Grace LAGOS NaN 60000.0
3 David Kano 28.0 NaN
4 Amina Lagos 25.0 50000.0
5 None Abuja 35.0 90000.0
Can you spot the problems?
- One customer has no name.
- One customer has no age.
- One customer has no purchase amount.
- Some city names use inconsistent capitalization.
- Amina's record appears twice.
We will correct these problems step by step.
10.2 Find Missing Values
Missing values occur when information is not available in a particular cell.
Pandas commonly represents missing numerical values as NaN.
To identify missing values, use isnull():
print(customers.isnull())
Output:
Name City Age Amount
0 False False False False
1 False False False False
2 False False True False
3 False False False True
4 False False False False
5 True False False False
True means the value is missing, while False means it is present.
You can count missing values in each column:
print(customers.isnull().sum())
Output:
Name 1
City 0
Age 1
Amount 1
dtype: int64
This tells us:
- The
Namecolumn has one missing value. - The
Agecolumn has one missing value. - The
Amountcolumn has one missing value. - The
Citycolumn has no missing values.
You can also check whether the entire DataFrame contains any missing values:
print(customers.isnull().values.any())
Output:
True
This is a quick way to check whether missing values exist anywhere in the dataset.
10.3 Remove Rows with Missing Values Using dropna()
Sometimes a record is too incomplete to be useful.
Pandas provides dropna() to remove rows containing missing values.
cleaned_customers = customers.dropna()
print(cleaned_customers)
Output:
Name City Age Amount
0 Amina Lagos 25.0 50000.0
1 Brian abuja 30.0 75000.0
4 Amina Lagos 25.0 50000.0
Rows 2, 3, and 5 were removed because each contained at least one missing value.
Notice that dropna() returns a new DataFrame by default. It does not automatically change the original customers DataFrame.
You can check the original:
print(customers.shape)
Output:
(6, 4)
And the cleaned version:
print(cleaned_customers.shape)
Output:
(3, 4)
Important: Removing rows is not always the best solution. If a dataset contains many records with missing information, deleting them could mean losing valuable data.
You should understand why values are missing before deciding what to do.
10.4 Remove Rows Based on a Particular Column
You may only want to remove records where a specific column is missing.
For example, a customer record without a name may be difficult to identify.
You can remove rows with missing names:
customers_without_missing_names = customers.dropna(
subset=["Name"]
)
print(customers_without_missing_names)
This removes only the row where Name is missing.
The other rows remain, even if their age or amount is missing.
This is useful when one column is particularly important to your analysis.
10.5 Fill Missing Values Using fillna()
Instead of removing incomplete records, you may decide to fill missing values.
Pandas provides fillna() for this purpose.
For example, you can replace missing ages with the average age:
customers_filled = customers.copy()
average_age = customers_filled["Age"].mean()
customers_filled["Age"] = customers_filled["Age"].fillna(
average_age
)
print(customers_filled[["Name", "Age"]])
Output:
Name Age
0 Amina 25.000000
1 Brian 30.000000
2 Grace 28.600000
3 David 28.000000
4 Amina 25.000000
5 None 35.000000
The average age of the available customers is 28.6, so pandas uses that value to fill Grace's missing age.
Notice that we used copy() before making changes. This allows us to work on a separate DataFrame without modifying the original.
You can also fill missing values with a fixed value.
For example, if you want missing purchase amounts to be marked as zero:
customers_filled["Amount"] = customers_filled["Amount"].fillna(0)
print(customers_filled["Amount"])
Output:
0 50000.0
1 75000.0
2 60000.0
3 0.0
4 50000.0
5 90000.0
Name: Amount, dtype: float64
However, be careful with this decision.
A missing purchase amount does not necessarily mean the customer spent nothing. It may mean the amount was never recorded.
Choose a replacement value based on what the data represents.
10.6 Find Duplicate Records
Duplicate records occur when the same information appears more than once.
In our dataset, Amina's record appears twice.
You can identify duplicates using duplicated():
print(customers.duplicated())
Output:
0 False
1 False
2 False
3 False
4 True
5 False
dtype: bool
The fifth row is marked True because it duplicates the first row.
You can count the duplicates:
print(customers.duplicated().sum())
Output:
1
This tells us there is one duplicate record.
10.7 Remove Duplicate Records Using drop_duplicates()
To remove duplicate rows, use drop_duplicates():
customers_unique = customers.drop_duplicates()
print(customers_unique)
Output:
Name City Age Amount
0 Amina Lagos 25.0 50000.0
1 Brian abuja 30.0 75000.0
2 Grace LAGOS NaN 60000.0
3 David Kano 28.0 NaN
5 None Abuja 35.0 90000.0
Only one copy of Amina's record remains.
By default, pandas keeps the first occurrence and removes later duplicates.
Just like dropna(), drop_duplicates() returns a new DataFrame by default.
You can also remove duplicates based on selected columns.
For example:
customers_unique_names = customers.drop_duplicates(
subset=["Name"]
)
This keeps only the first occurrence of each name.
Be careful with this approach: two different customers can have the same name. Removing records based only on names may accidentally delete valid customers.
10.8 Correct Inconsistent Text
Look at the City column:
Lagos
abuja
LAGOS
Kano
Lagos
Abuja
These values refer to the same cities, but their capitalization is inconsistent.
This can create problems when grouping or filtering data.
Pandas provides string methods that help standardise text.
To convert city names to title case:
customers_clean = customers.copy()
customers_clean["City"] = customers_clean["City"].str.title()
print(customers_clean["City"])
Output:
0 Lagos
1 Abuja
2 Lagos
3 Kano
4 Lagos
5 Abuja
Name: City, dtype: object
Now the city names use consistent capitalization.
Other useful string methods include:
customers_clean["City"].str.lower()
This converts text to lowercase.
customers_clean["City"].str.upper()
This converts text to uppercase.
customers_clean["City"].str.strip()
This removes unnecessary spaces from the beginning and end of text values.
These methods are useful when working with names, addresses, categories, and other text columns.
10.9 Rename Columns Using rename()
Sometimes column names are unclear or difficult to work with.
For example, a dataset might contain a column named Amount.
You may prefer a more descriptive name such as Purchase_Amount.
You can rename it using rename():
customers_renamed = customers.rename(
columns={
"Amount": "Purchase_Amount",
"Name": "Customer_Name"
}
)
print(customers_renamed.columns)
Output:
Index(['Customer_Name', 'City', 'Age', 'Purchase_Amount'], dtype='object')
You can rename one column or several columns at the same time.
Clear column names make your code easier to understand.
10.10 Convert Data Types Using astype()
Sometimes data is stored in the wrong format.
For example, an age column might be stored as text instead of numbers.
Let's create a small example:
data = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace"],
"Age": ["25", "30", "28"]
})
print(data.dtypes)
Output:
Name object
Age object
dtype: object
The Age column is stored as text.
You can convert it to integers:
data["Age"] = data["Age"].astype(int)
print(data.dtypes)
Output:
Name object
Age int64
dtype: object
Now the age values are stored as numbers, so you can perform numerical calculations.
For example:
print(data["Age"].mean())
Output:
27.666666666666668
Important: astype(int) works when the values can be converted into whole numbers. If the column contains text such as "unknown", the conversion will fail.
You need to inspect and handle such values before converting the column.
10.11 Identify Unusual Values
Not every data problem involves missing values or duplicates.
Sometimes a value exists but does not make sense.
Imagine a customer dataset containing these ages:
ages = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David"],
"Age": [25, 30, -5, 150]
})
The values -5 and 150 may indicate data-entry errors, depending on the context.
You can filter for suspicious ages:
unusual_ages = ages[
(ages["Age"] < 0) |
(ages["Age"] > 100)
]
print(unusual_ages)
Output:
Name Age
2 Grace -5
3 David 150
Pandas has identified the values that fall outside the range we chose to investigate.
This does not automatically prove that the records are wrong. You should check the original source or confirm the correct values before changing them.
10.12 A Complete Practical Example
Let's bring several cleaning techniques together.
Imagine you have received a customer dataset and want to prepare it for analysis.
import pandas as pd
customers = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David", "Amina", None],
"City": ["Lagos", "abuja", "LAGOS", "Kano", "Lagos", "Abuja"],
"Age": [25, 30, None, 28, 25, 35],
"Amount": [50000, 75000, 60000, None, 50000, 90000]
})
# Make a copy so the original data remains unchanged
cleaned = customers.copy()
# Remove duplicate records
cleaned = cleaned.drop_duplicates()
# Standardise city names
cleaned["City"] = cleaned["City"].str.title()
# Fill missing ages with the average age
average_age = cleaned["Age"].mean()
cleaned["Age"] = cleaned["Age"].fillna(average_age)
# Fill missing purchase amounts with zero
cleaned["Amount"] = cleaned["Amount"].fillna(0)
# Rename the Amount column
cleaned = cleaned.rename(
columns={"Amount": "Purchase_Amount"}
)
print(cleaned)
Output:
Name City Age Purchase_Amount
0 Amina Lagos 25.000000 50000.0
1 Brian Abuja 30.000000 75000.0
2 Grace Lagos 29.666667 60000.0
3 David Kano 28.000000 0.0
5 None Abuja 35.000000 90000.0
Notice that we removed the duplicate Amina record, standardised the city names, filled missing values, and renamed a column.
The average age is now approximately 29.67 because the duplicate record was removed before calculating the average.
This example shows how different cleaning techniques can work together.
However, the choices we made are assumptions for this example. In a real project, you should decide how to handle missing amounts and missing customer names based on the meaning of the data.
10.13 What You Should Remember
Here are the important data-cleaning methods covered in this section:
| Method | Purpose |
|---|---|
isnull() |
Identifies missing values |
isnull().sum() |
Counts missing values in each column |
dropna() |
Removes rows with missing values |
fillna() |
Replaces missing values |
duplicated() |
Identifies duplicate rows |
drop_duplicates() |
Removes duplicate rows |
.str.title() |
Converts text to title case |
.str.lower() |
Converts text to lowercase |
.str.strip() |
Removes extra spaces around text |
rename() |
Renames columns |
astype() |
Converts data types |
The most important lesson is that cleaning data is not just about making a dataset look neat. It is about making sure the data is suitable for the questions you want to answer.
Do not automatically delete or replace information without understanding what it means. A cleaning decision that is wrong for the data can be just as harmful as leaving the original problem untouched.
In the next section, we will learn how to sort data and rank records, allowing us to identify the highest values, lowest values, and top-performing records in a dataset.
11. Sorting and Ranking Data in Pandas
In the previous section, you learned how to clean a dataset by handling missing values, removing duplicates, and correcting inconsistent information.
Now that you know how to prepare data, let's learn how to organise it.
Imagine you are analysing examination results at a school in Accra, Ghana. You may want to:
- Arrange students from the highest score to the lowest.
- Find the student with the highest score.
- Identify the three best-performing students.
- Arrange students alphabetically.
- Sort students by class and then by score.
Pandas makes these tasks straightforward using sorting and ranking methods.
In this section, you will learn how to use sort_values(), sort_index(), nlargest(), nsmallest(), and rank().
11.1 Create a Dataset to Work With
Let's create a student performance dataset.
import pandas as pd
students = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David", "Lydia", "Samuel"],
"Class": ["SHS1", "SHS1", "SHS2", "SHS2", "SHS1", "SHS2"],
"Score": [85, 72, 91, 45, 88, 67],
"Attendance": [95, 88, 98, 70, 92, 85]
})
print(students)
Output:
Name Class Score Attendance
0 Amina SHS1 85 95
1 Brian SHS1 72 88
2 Grace SHS2 91 98
3 David SHS2 45 70
4 Lydia SHS1 88 92
5 Samuel SHS2 67 85
We will use this DataFrame throughout the section.
11.2 Sort Data in Ascending Order
Ascending order means arranging values from the smallest to the largest.
Suppose you want to arrange students according to their scores, starting with the lowest score.
Use sort_values():
students_sorted = students.sort_values(by="Score")
print(students_sorted)
Output:
Name Class Score Attendance
3 David SHS2 45 70
5 Samuel SHS2 67 85
1 Brian SHS1 72 88
0 Amina SHS1 85 95
4 Lydia SHS1 88 92
2 Grace SHS2 91 98
Notice that the scores now appear in ascending order:
45, 67, 72, 85, 88, 91.
The row indexes remain attached to their original records. Sorting changes the order in which rows appear, not the identity of the records.
By default, sort_values() uses ascending order.
11.3 Sort Data in Descending Order
Descending order means arranging values from the largest to the smallest.
If you want to see the highest-performing students first, use ascending=False.
students_sorted = students.sort_values(
by="Score",
ascending=False
)
print(students_sorted)
Output:
Name Class Score Attendance
2 Grace SHS2 91 98
4 Lydia SHS1 88 92
0 Amina SHS1 85 95
1 Brian SHS1 72 88
5 Samuel SHS2 67 85
3 David SHS2 45 70
Now the highest score appears first.
This is useful when creating leaderboards, identifying top-performing students, or organising sales from highest to lowest.
11.4 Sort Using Multiple Columns
Sometimes you need to sort data using more than one column.
For example, imagine you want to organise students by class and then arrange their scores from highest to lowest within each class.
You can pass a list of column names to by:
students_sorted = students.sort_values(
by=["Class", "Score"],
ascending=[True, False]
)
print(students_sorted)
Output:
Name Class Score Attendance
0 Amina SHS1 85 95
4 Lydia SHS1 88 92
1 Brian SHS1 72 88
2 Grace SHS2 91 98
5 Samuel SHS2 67 85
3 David SHS2 45 70
The first column, Class, is sorted in ascending order.
The second column, Score, is sorted in descending order.
A note about the output: Within SHS1, the scores should be 88, 85, and 72. The correctly sorted output is:
Name Class Score Attendance
4 Lydia SHS1 88 92
0 Amina SHS1 85 95
1 Brian SHS1 72 88
2 Grace SHS2 91 98
5 Samuel SHS2 67 85
3 David SHS2 45 70
This illustrates why it is important to check the output rather than assume that code has produced the expected order.
11.5 Sort Alphabetically
You can also sort text columns.
For example, to arrange students alphabetically by name:
students_sorted = students.sort_values(by="Name")
print(students_sorted[["Name", "Score"]])
Output:
Name Score
0 Amina 85
1 Brian 72
3 David 45
2 Grace 91
4 Lydia 88
5 Samuel 67
This is useful when preparing lists, organising customer records, or making datasets easier to read.
11.6 Sort Using the Index with sort_index()
So far, we have sorted rows using column values.
Pandas also allows you to sort rows using their index labels.
Use sort_index():
students_index_sorted = students.sort_index(ascending=False)
print(students_index_sorted)
Output:
Name Class Score Attendance
5 Samuel SHS2 67 85
4 Lydia SHS1 88 92
3 David SHS2 45 70
2 Grace SHS2 91 98
1 Brian SHS1 72 88
0 Amina SHS1 85 95
The rows are now arranged according to their index labels, from 5 down to 0.
The difference is:
sort_values()sorts using values in one or more columns.sort_index()sorts using index labels.
11.7 Find the Highest Values with nlargest()
Sometimes you do not need to sort the entire dataset.
You may only want to find the three students with the highest scores.
Pandas provides nlargest():
top_students = students.nlargest(3, "Score")
print(top_students)
Output:
Name Class Score Attendance
2 Grace SHS2 91 98
4 Lydia SHS1 88 92
0 Amina SHS1 85 95
The number 3 tells pandas to return the three largest values in the Score column.
This is a convenient way to identify top performers.
You could also use sorting and select the first three rows:
top_students = students.sort_values(
by="Score",
ascending=False
).head(3)
Both approaches can answer the same question.
11.8 Find the Lowest Values with nsmallest()
The opposite of nlargest() is nsmallest().
It returns the records with the smallest values in a column.
For example, find the three students with the lowest scores:
lowest_students = students.nsmallest(3, "Score")
print(lowest_students)
Output:
Name Class Score Attendance
3 David SHS2 45 70
5 Samuel SHS2 67 85
1 Brian SHS1 72 88
This can help identify students who may need additional academic support.
It is also useful in business analysis when you want to find the lowest-selling products or smallest transactions.
11.9 Rank Values Using rank()
Sorting changes the order of rows.
Ranking, on the other hand, assigns a position or rank to each value.
Suppose you want to assign each student a position based on their examination score.
Use rank():
students["Rank"] = students["Score"].rank(
ascending=False
)
print(students[["Name", "Score", "Rank"]])
Output:
Name Score Rank
0 Amina 85 3.0
1 Brian 72 4.0
2 Grace 91 1.0
3 David 45 6.0
4 Lydia 88 2.0
5 Samuel 67 5.0
Because ascending=False, the highest score receives rank 1.
The ranks are stored as numbers in a new column called Rank.
Notice that the original row order has not changed.
This is the key difference:
- Sorting rearranges the rows.
- Ranking adds a value that describes each row's position.
11.10 Understand Tied Ranks
What happens when two students receive the same score?
Let's create a small example:
scores = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David"],
"Score": [90, 80, 80, 60]
})
scores["Rank"] = scores["Score"].rank(
ascending=False
)
print(scores)
Output:
Name Score Rank
0 Amina 90 1.0
1 Brian 80 2.5
2 Grace 80 2.5
3 David 60 4.0
Brian and Grace have the same score, so pandas gives them the average of the positions they occupy: 2 and 3.
The average is 2.5.
You can change how pandas handles tied ranks using the method argument.
For example, use method="min":
scores["Rank"] = scores["Score"].rank(
ascending=False,
method="min"
)
print(scores)
Output:
Name Score Rank
0 Amina 90 1.0
1 Brian 80 2.0
2 Grace 80 2.0
3 David 60 4.0
Both Brian and Grace receive rank 2. The next rank is 4.
Other useful ranking methods include:
| Method | How it handles ties |
|---|---|
average |
Gives tied values the average rank (default) |
min |
Gives tied values the lowest rank |
max |
Gives tied values the highest rank |
first |
Ranks tied values according to their order in the dataset |
dense |
Gives tied values the same rank without skipping the next rank |
For example, with method="dense", the ranks would be 1, 2, 2, and 3.
Choose the ranking method that matches the rules of your analysis.
11.11 Sort and Rank in a Practical Example
Let's combine what you have learned.
Imagine a school administrator wants to prepare a performance report showing students from highest to lowest, with their ranks.
import pandas as pd
students = pd.DataFrame({
"Name": ["Amina", "Brian", "Grace", "David", "Lydia", "Samuel"],
"Class": ["SHS1", "SHS1", "SHS2", "SHS2", "SHS1", "SHS2"],
"Score": [85, 72, 91, 45, 88, 67]
})
# Assign ranks based on scores
students["Rank"] = students["Score"].rank(
ascending=False,
method="min"
)
# Arrange students from highest to lowest score
report = students.sort_values(
by="Score",
ascending=False
)
print(report)
Output:
Name Class Score Rank
2 Grace SHS2 91 1.0
4 Lydia SHS1 88 2.0
0 Amina SHS1 85 3.0
1 Brian SHS1 72 4.0
5 Samuel SHS2 67 5.0
3 David SHS2 45 6.0
The administrator now has an organised performance report with each student's rank.
11.12 What You Should Remember
Here are the main methods covered in this section:
| Method | Purpose |
|---|---|
sort_values() |
Sorts rows using column values |
sort_index() |
Sorts rows using index labels |
nlargest() |
Returns the largest values |
nsmallest() |
Returns the smallest values |
rank() |
Assigns ranks to values |
The important lesson is that sorting and ranking help you organise data and identify meaningful patterns.
When someone asks you to find the best-performing students, highest-selling products, or lowest expenses, pandas gives you simple tools to answer those questions.
In the next section, we will learn how to group data and calculate summaries using groupby(). This will help you answer questions such as which class has the highest average score or which product category generates the most revenue.
12. Grouping and Aggregating Data in Pandas
In the previous section, you learned how to sort and rank records in a DataFrame.
Now imagine you are analysing sales records from a supermarket in Nairobi, Kenya.
You have hundreds of transactions, but you want to answer questions such as:
- How much revenue did each product generate?
- Which branch made the most sales?
- What was the average transaction value in each city?
- How many transactions were recorded for each product?
- Which product category performed best?
Looking at individual rows will not answer these questions easily.
You need a way to organise records into groups and calculate summaries for each group.
Pandas provides a powerful method called groupby() for this purpose.
In this section, you will learn how to group data, calculate totals and averages, count records, use multiple aggregation functions, and group data using more than one column.
12.1 Create a Dataset to Work With
Let's create a sales dataset for a business operating in Nairobi.
Each row represents a sales transaction.
import pandas as pd
sales = pd.DataFrame({
"Product": [
"Rice", "Beans", "Rice", "Maize Flour",
"Beans", "Rice", "Maize Flour", "Beans"
],
"Category": [
"Grains", "Legumes", "Grains", "Flour",
"Legumes", "Grains", "Flour", "Legumes"
],
"Branch": [
"Nairobi", "Mombasa", "Nairobi", "Kisumu",
"Mombasa", "Kisumu", "Nairobi", "Nairobi"
],
"Quantity": [10, 5, 8, 12, 7, 6, 9, 4],
"Revenue_KES": [
15000, 9000, 12000, 18000,
12600, 9000, 13500, 7200
]
})
print(sales)
Output:
Product Category Branch Quantity Revenue_KES
0 Rice Grains Nairobi 10 15000
1 Beans Legumes Mombasa 5 9000
2 Rice Grains Nairobi 8 12000
3 Maize Flour Flour Kisumu 12 18000
4 Beans Legumes Mombasa 7 12600
5 Rice Grains Kisumu 6 9000
6 Maize Flour Flour Nairobi 9 13500
7 Beans Legumes Nairobi 4 7200
The dataset contains information about products, categories, branches, quantities sold, and revenue.
Let's use it to answer practical business questions.
12.2 What Does groupby() Do?
The groupby() method allows you to divide a DataFrame into groups based on shared values in a column.
For example, the Category column contains three categories:
- Grains
- Legumes
- Flour
You can group the sales records by category:
grouped = sales.groupby("Category")
print(grouped["Revenue_KES"].sum())
Output:
Category
Flour 31500
Grains 36000
Legumes 28800
Name: Revenue_KES, dtype: int64
What happened?
Pandas:
- Looked at the
Categorycolumn. - Placed records with the same category into groups.
- Added together the revenue for each group.
- Returned the total revenue for each category.
The result tells us that:
- Flour generated KES 31,500.
- Grains generated KES 36,000.
- Legumes generated KES 28,800.
This is much easier than manually adding the values in each category.
12.3 Calculate Total Revenue with sum()
Suppose you want to know how much revenue each branch generated.
You can group the data by Branch and calculate the total revenue.
branch_revenue = sales.groupby("Branch")["Revenue_KES"].sum()
print(branch_revenue)
Output:
Branch
Kisumu 27000
Mombasa 21600
Nairobi 47700
Name: Revenue_KES, dtype: int64
This tells us that Nairobi generated the highest revenue.
You can also calculate the total quantity sold by each branch:
branch_quantity = sales.groupby("Branch")["Quantity"].sum()
print(branch_quantity)
Output:
Branch
Kisumu 18
Mombasa 12
Nairobi 31
Name: Quantity, dtype: int64
Now you can compare the branches based on the number of units sold rather than revenue.
12.4 Calculate Average Values with mean()
Totals are useful, but sometimes you want to understand average performance.
For example, what was the average revenue per transaction for each branch?
average_revenue = sales.groupby("Branch")["Revenue_KES"].mean()
print(average_revenue)
Output:
Branch
Kisumu 13500.0
Mombasa 10800.0
Nairobi 11925.0
Name: Revenue_KES, dtype: float64
This shows the average revenue recorded in each transaction for each branch.
Notice that Nairobi has the highest total revenue, but Kisumu has the highest average revenue per transaction.
This demonstrates why different measurements can tell different stories about the same business.
12.5 Count Records with count() and size()
Sometimes you want to know how many transactions belong to each group.
You can use size():
transaction_count = sales.groupby("Branch").size()
print(transaction_count)
Output:
Branch
Kisumu 2
Mombasa 2
Nairobi 4
dtype: int64
This tells us that:
- Kisumu recorded 2 transactions.
- Mombasa recorded 2 transactions.
- Nairobi recorded 4 transactions.
You can also use count():
print(sales.groupby("Branch")["Revenue_KES"].count())
Output:
Branch
Kisumu 2
Mombasa 2
Nairobi 4
Name: Revenue_KES, dtype: int64
What is the difference?
size()counts all rows in each group.count()counts non-missing values in the selected column.
If a group contains missing revenue values, count() will exclude those missing values, while size() will still count the rows.
12.6 Find the Highest and Lowest Values in Each Group
Pandas also allows you to calculate the maximum and minimum values within each group.
For example, find the highest transaction revenue recorded at each branch:
highest_revenue = sales.groupby("Branch")["Revenue_KES"].max()
print(highest_revenue)
Output:
Branch
Kisumu 18000
Mombasa 12600
Nairobi 15000
Name: Revenue_KES, dtype: int64
To find the lowest transaction revenue:
lowest_revenue = sales.groupby("Branch")["Revenue_KES"].min()
print(lowest_revenue)
Output:
Branch
Kisumu 9000
Mombasa 9000
Nairobi 7200
Name: Revenue_KES, dtype: int64
These calculations help you understand the range of transaction values in each branch.
12.7 Use Multiple Aggregation Functions with agg()
What if you want to calculate several statistics at the same time?
For example, you want to see the total revenue, average revenue, highest transaction, and number of transactions for each branch.
Instead of writing separate groupby statements, you can use agg().
branch_summary = sales.groupby("Branch").agg({
"Revenue_KES": ["sum", "mean", "max", "count"],
"Quantity": "sum"
})
print(branch_summary)
Output:
Revenue_KES Quantity
sum mean max count sum
Branch
Kisumu 27000 13500.0 18000 2 18
Mombasa 21600 10800.0 12600 2 12
Nairobi 47700 11925.0 15000 4 31
This produces a summary table containing several statistics.
For each branch, you can see:
- Total revenue.
- Average transaction revenue.
- Highest transaction revenue.
- Number of transactions.
- Total quantity sold.
This is especially useful when preparing reports.
12.8 Group by Multiple Columns
Sometimes you need to group data using more than one column.
For example, you may want to know how much revenue each product generated at each branch.
You can group by both Branch and Product.
product_branch_sales = sales.groupby(
["Branch", "Product"]
)["Revenue_KES"].sum()
print(product_branch_sales)
Output:
Branch Product
Kisumu Maize Flour 18000
Rice 9000
Mombasa Beans 21600
Nairobi Beans 7200
Maize Flour 13500
Rice 27000
Name: Revenue_KES, dtype: int64
Pandas first groups the records by branch and then groups them by product within each branch.
This helps you understand which products are performing well in different locations.
For example, Rice generated KES 27,000 in Nairobi but KES 9,000 in Kisumu.
12.9 Use reset_index() to Make Results Easier to Work With
When you use groupby(), pandas often places the grouping column in the result's index.
For example:
branch_revenue = sales.groupby("Branch")["Revenue_KES"].sum()
print(branch_revenue)
The branch names appear as index labels.
If you want the branch names to become a normal column again, use reset_index():
branch_revenue = sales.groupby("Branch")["Revenue_KES"].sum().reset_index()
print(branch_revenue)
Output:
Branch Revenue_KES
0 Kisumu 27000
1 Mombasa 21600
2 Nairobi 47700
This is useful when you want to save the results, create charts, or perform further analysis.
12.10 Give Aggregated Columns Clear Names
When you use several aggregation functions, pandas may create multi-level column names.
You can make the result easier to understand by using named aggregation.
For example:
branch_summary = sales.groupby("Branch").agg(
Total_Revenue=("Revenue_KES", "sum"),
Average_Revenue=("Revenue_KES", "mean"),
Transactions=("Revenue_KES", "count"),
Total_Quantity=("Quantity", "sum")
).reset_index()
print(branch_summary)
Output:
Branch Total_Revenue Average_Revenue Transactions Total_Quantity
0 Kisumu 27000 13500.0 2 18
1 Mombasa 21600 10800.0 2 12
2 Nairobi 47700 11925.0 4 31
This approach lets you choose meaningful names for the resulting columns.
It is especially useful when you are preparing a clean summary for someone else to read.
12.11 A Complete Practical Example
Let's imagine a business manager wants a report showing the total and average revenue generated by each branch.
We can solve this with a few lines of pandas code.
import pandas as pd
sales = pd.DataFrame({
"Product": [
"Rice", "Beans", "Rice", "Maize Flour",
"Beans", "Rice", "Maize Flour", "Beans"
],
"Branch": [
"Nairobi", "Mombasa", "Nairobi", "Kisumu",
"Mombasa", "Kisumu", "Nairobi", "Nairobi"
],
"Quantity": [10, 5, 8, 12, 7, 6, 9, 4],
"Revenue_KES": [
15000, 9000, 12000, 18000,
12600, 9000, 13500, 7200
]
})
# Group transactions by branch and calculate summary statistics
report = sales.groupby("Branch").agg(
Total_Revenue=("Revenue_KES", "sum"),
Average_Revenue=("Revenue_KES", "mean"),
Total_Quantity=("Quantity", "sum"),
Number_of_Transactions=("Revenue_KES", "count")
).reset_index()
# Display the report
print(report)
Output:
Branch Total_Revenue Average_Revenue Total_Quantity Number_of_Transactions
0 Kisumu 27000 13500.0 18 2
1 Mombasa 21600 10800.0 12 2
2 Nairobi 47700 11925.0 31 4
The manager can now compare the branches without manually adding individual transactions.
Nairobi generated the highest total revenue, while Kisumu recorded the highest average revenue per transaction.
That is the value of grouping and aggregation: they help turn many individual records into useful business information.
12.12 What You Should Remember
Here are the main methods covered in this section:
| Method | Purpose |
|---|---|
groupby() |
Divides records into groups |
sum() |
Calculates totals |
mean() |
Calculates averages |
count() |
Counts non-missing values |
size() |
Counts rows in each group |
max() |
Finds the highest value |
min() |
Finds the lowest value |
agg() |
Applies multiple aggregation functions |
reset_index() |
Converts index labels back into columns |
The most important lesson is that grouping helps you answer questions about categories, locations, products, departments, and other shared characteristics in your data.
Instead of looking at hundreds of individual transactions, you can summarise them into a smaller report that makes patterns easier to understand.
In the next section, we will learn how to combine DataFrames using merge(), join(), and concat(). These techniques are useful when information is stored in separate tables and you need to bring it together for analysis.
About the author
- Passionate about the intersection of data and technology in practical problem solving. Writing about Python, SQL, PostgreSQL, and Power BI; creating practical systems that transform data into meaningful insights and facilitate better decision-making. Always learning, building, and sharing knowledge along the way. . .
Comments
Join the discussionNo comments yet. Be the first to comment.



