Python for Data Science & Automation · Module 3: Data Manipulation & Engineering with Pandas · Lesson 15 of 34

3.5 Transformation & Aggregation — GroupBy, Pivot Tables, Merge, Join & Concatenation

3.5 Data Transformation & Aggregation with Pandas

After cleaning a dataset, the next step is often to transform, summarize, and combine data.

In real-world Data Science projects, information is rarely stored in one perfectly organized table. You may need to calculate department-wise averages, summarize sales by region, combine customer and transaction data, or reshape a dataset for reporting.

Clean Data → Transform → Aggregate → Combine → Analyze
In this lesson, you will learn:
  • groupby() for grouped analysis
  • Aggregation functions such as sum(), mean(), count(), and max()
  • Multiple aggregations using agg()
  • Pivot tables using pivot_table()
  • Combining DataFrames using merge()
  • Joining DataFrames using join()
  • Stacking datasets using concat()
  • Choosing between merge, join, and concatenation
  • Building practical transformation pipelines

1. Sample Dataset

We will use a student-performance dataset throughout this lesson.

import pandas as pd

data = {
    "Student": [
        "Alice",
        "Brian",
        "Clara",
        "David",
        "Eva",
        "Frank"
    ],

    "Department": [
        "Science",
        "Commerce",
        "Science",
        "Commerce",
        "Science",
        "Commerce"
    ],

    "Gender": [
        "Female",
        "Male",
        "Female",
        "Male",
        "Female",
        "Male"
    ],

    "Math": [
        85,
        78,
        92,
        74,
        88,
        81
    ],

    "Science": [
        90,
        72,
        95,
        68,
        91,
        75
    ]
}

df = pd.DataFrame(data)

print(df)

This DataFrame contains categorical columns such as Department and Gender, together with numerical columns such as Math and Science.

2. What Is Data Transformation?

Data transformation means changing data from one form into another form so that it becomes more useful for analysis.

Examples include:

  • Creating a new calculated column
  • Grouping records by category
  • Reshaping data using pivot tables
  • Combining multiple DataFrames
  • Aggregating thousands of records into summary statistics

Example:

df["Total"] = (
    df["Math"] +
    df["Science"]
)

Here, a new Total column has been created from two existing columns.

3. What Is Data Aggregation?

Aggregation means combining multiple records to produce a summary value.

For example, instead of looking at every student's score individually, you may want to calculate the average score for each department.

Aggregation Purpose
sum() Total
mean() Average
median() Middle value
min() Smallest value
max() Largest value
count() Number of observations
std() Standard deviation

4. Introduction to groupby()

The groupby() method divides a DataFrame into groups based on one or more columns.

General syntax:

df.groupby("column")

For example:

df.groupby("Department")

This creates groups based on the values in the Department column.

Split → Apply → Combine

This is the fundamental idea behind pandas grouping operations.

5. Calculating Group-Wise Averages

To calculate the average score by department:

df.groupby(
    "Department"
)["Math"].mean()

The operation:

  1. Groups rows by Department.
  2. Selects the Math column.
  3. Calculates the mean for each department.

Example result:

Department
Commerce    77.67
Science     88.33
Name: Math, dtype: float64

6. Using sum() with groupby()

df.groupby(
    "Department"
)["Math"].sum()

This calculates the total Mathematics score for each department.

The same pattern can be used for other aggregation functions.

df.groupby("Department")["Math"].min()

df.groupby("Department")["Math"].max()

df.groupby("Department")["Math"].median()

df.groupby("Department")["Math"].count()

7. Grouping by Multiple Columns

You can group data using more than one column.

df.groupby(
    ["Department", "Gender"]
)["Math"].mean()

This calculates the average Mathematics score for each combination of department and gender.

Remember:

Use a list when grouping by multiple columns:

df.groupby(
    ["Column1", "Column2"]
)

8. Grouping Multiple Numerical Columns

You can calculate statistics for multiple numerical columns at once.

df.groupby(
    "Department"
)[
    ["Math", "Science"]
].mean()

This returns the average Mathematics and Science scores for each department.

9. Multiple Aggregations with agg()

The agg() method allows multiple aggregation functions to be applied together.

df.groupby(
    "Department"
)["Math"].agg(
    ["mean", "min", "max"]
)

This produces a summary containing:

  • Average score
  • Minimum score
  • Maximum score

10. Different Aggregations for Different Columns

A dictionary can specify different aggregation functions for different columns.

df.groupby("Department").agg({
    "Math": "mean",
    "Science": "max"
})

Here:

  • Math → average
  • Science → maximum

11. Named Aggregation

Named aggregation allows you to specify meaningful names for calculated columns.

summary = df.groupby(
    "Department"
).agg(
    average_math=("Math", "mean"),
    highest_science=("Science", "max"),
    student_count=("Student", "count")
)

print(summary)

This produces a clean, report-friendly summary.

12. Using as_index=False

By default, grouping columns often become the index of the resulting DataFrame.

If you want the grouping column to remain an ordinary column, use:

df.groupby(
    "Department",
    as_index=False
)["Math"].mean()

This can make the result easier to work with in later transformations.

13. Using reset_index()

Another common approach is to reset the resulting index.

summary = (
    df.groupby("Department")["Math"]
    .mean()
    .reset_index()
)

Now Department becomes a regular DataFrame column.

14. Introduction to Pivot Tables

A pivot table summarizes data across one or more dimensions.

pandas provides the pivot_table() function for this purpose.

General syntax:

pd.pivot_table(
    data,
    values=...,
    index=...,
    columns=...,
    aggfunc=...
)

15. Creating a Pivot Table

Calculate average scores by department and gender:

pivot = pd.pivot_table(
    df,
    values="Math",
    index="Department",
    columns="Gender",
    aggfunc="mean"
)

print(pivot)

Conceptually, the resulting table places:

  • Department on rows
  • Gender on columns
  • Average Math score in the cells

16. Pivoting Multiple Values

Multiple numerical columns can be included.

pd.pivot_table(
    df,
    values=["Math", "Science"],
    index="Department",
    columns="Gender",
    aggfunc="mean"
)

This produces a more detailed summary.

17. Handling Missing Combinations in Pivot Tables

Some combinations may not exist in the source data. You can specify a replacement value using fill_value.

pd.pivot_table(
    df,
    values="Math",
    index="Department",
    columns="Gender",
    aggfunc="mean",
    fill_value=0
)
Important:

fill_value=0 means that missing combinations in the resulting pivot table are displayed as zero. It does not necessarily mean that the original data contained an actual score of zero.

18. Counting Records with Pivot Tables

A pivot table can also be used for counting.

pd.pivot_table(
    df,
    values="Student",
    index="Department",
    columns="Gender",
    aggfunc="count",
    fill_value=0
)

This can show how many students belong to each department-gender combination.

19. groupby() vs pivot_table()

groupby() pivot_table()
Groups data for aggregation. Creates a spreadsheet-style summary.
Very flexible for transformations. Excellent for multidimensional summaries.
Useful for custom analytical pipelines. Useful for reports and cross-tabulated analysis.
Often produces Series or DataFrame results. Produces a reshaped summary table.

20. Combining DataFrames with merge()

The merge() function combines DataFrames using one or more common columns.

It is conceptually similar to a database JOIN.

Consider two DataFrames.

students = pd.DataFrame({
    "StudentID": [101, 102, 103, 104],
    "Name": [
        "Alice",
        "Brian",
        "Clara",
        "David"
    ]
})

scores = pd.DataFrame({
    "StudentID": [101, 102, 103, 104],
    "Score": [85, 78, 92, 88]
})

Both DataFrames contain StudentID.

21. Inner Merge

An inner merge keeps only matching keys in both DataFrames.

result = pd.merge(
    students,
    scores,
    on="StudentID",
    how="inner"
)

print(result)

This combines student names with their scores where StudentID exists in both tables.

22. Left Merge

A left merge keeps every row from the left DataFrame and matches corresponding rows from the right DataFrame where possible.

result = pd.merge(
    students,
    scores,
    on="StudentID",
    how="left"
)
Think:

how="left" → Keep everything from the left table.

23. Right Merge

A right merge keeps every row from the right DataFrame.

result = pd.merge(
    students,
    scores,
    on="StudentID",
    how="right"
)

Matching information from the left DataFrame is included where available.

24. Outer Merge

An outer merge keeps keys from both DataFrames.

result = pd.merge(
    students,
    scores,
    on="StudentID",
    how="outer"
)

If a matching record does not exist on one side, pandas represents the missing information with a missing value.

25. Types of Merge

Merge Type What It Keeps
inner Only matching keys.
left All left keys + matching right keys.
right All right keys + matching left keys.
outer All keys from both DataFrames.

26. Merging Columns with Different Names

Sometimes the key has a different name in each DataFrame.

students = pd.DataFrame({
    "StudentID": [101, 102, 103],
    "Name": [
        "Alice",
        "Brian",
        "Clara"
    ]
})

scores = pd.DataFrame({
    "ID": [101, 102, 103],
    "Score": [85, 78, 92]
})

result = pd.merge(
    students,
    scores,
    left_on="StudentID",
    right_on="ID"
)

Use:

  • left_on for the key in the left DataFrame
  • right_on for the key in the right DataFrame

27. Merging Using Multiple Keys

More than one column can be used as the matching key.

result = pd.merge(
    df1,
    df2,
    on=["StudentID", "Year"],
    how="inner"
)

A row must match according to the specified key combination.

28. Handling Duplicate Column Names

If both DataFrames contain columns with the same name that are not used as the merge key, pandas can add suffixes.

result = pd.merge(
    df1,
    df2,
    on="StudentID",
    suffixes=("_left", "_right")
)

This helps distinguish the two versions of the column.

29. Joining DataFrames with join()

The join() method combines DataFrames primarily based on their indexes.

Example:

left = pd.DataFrame(
    {
        "Name": [
            "Alice",
            "Brian",
            "Clara"
        ]
    },
    index=[101, 102, 103]
)

right = pd.DataFrame(
    {
        "Score": [
            85,
            78,
            92
        ]
    },
    index=[101, 102, 103]
)

result = left.join(right)

print(result)

30. Join Types

Like merging, join() can use different join strategies.

left.join(
    right,
    how="inner"
)

Common options include:

  • left
  • right
  • inner
  • outer

31. Joining Using a Column

join() can also be used with a column from the left DataFrame and the index of the right DataFrame.

result = left.join(
    right.set_index("StudentID"),
    on="StudentID"
)

In practice, merge() is often more natural when both sides use ordinary columns as keys.

32. Combining DataFrames with concat()

pd.concat() combines pandas objects along a specified axis.

It is commonly used to:

  • Stack DataFrames vertically
  • Combine DataFrames horizontally
  • Append datasets with compatible structures

33. Concatenating Rows

Suppose two datasets contain records from different periods:

jan = pd.DataFrame({
    "Name": ["Alice", "Brian"],
    "Score": [85, 78]
})

feb = pd.DataFrame({
    "Name": ["Clara", "David"],
    "Score": [92, 88]
})

result = pd.concat(
    [jan, feb],
    axis=0
)

print(result)

axis=0 combines the DataFrames vertically.

34. Using ignore_index=True

When vertically concatenating DataFrames, the original indexes may be retained.

To create a fresh sequential index:

result = pd.concat(
    [jan, feb],
    ignore_index=True
)
Common Practice:

Use ignore_index=True when combining datasets that should form one new continuous table and the original indexes are not meaningful.

35. Concatenating Columns

Set axis=1 to concatenate horizontally.

names = pd.DataFrame({
    "Name": [
        "Alice",
        "Brian",
        "Clara"
    ]
})

scores = pd.DataFrame({
    "Score": [
        85,
        78,
        92
    ]
})

result = pd.concat(
    [names, scores],
    axis=1
)

The DataFrames are aligned according to their indexes.

36. concat() vs merge() vs join()

Method Main Idea Typical Use
concat() Stack or align objects. Combine datasets vertically or horizontally.
merge() Match records using keys. Database-style relational joins.
join() Combine using indexes. Index-based DataFrame combination.

37. Easy Way to Remember the Three

CONCAT → Put datasets together
MERGE → Match datasets using keys
JOIN → Match primarily using indexes

Example:

  • January + February records → concat
  • Student table + Score table using StudentID → merge
  • DataFrames already indexed by StudentID → join

38. Practical Example — Department Analysis

Suppose an institution wants to determine the average and maximum scores for every department.

summary = df.groupby(
    "Department"
).agg(
    average_math=("Math", "mean"),
    maximum_math=("Math", "max"),
    average_science=("Science", "mean"),
    maximum_science=("Science", "max")
)

print(summary)

This produces a compact analytical report without manually calculating each department.

39. Practical Example — Department × Gender

A pivot table can provide a cross-sectional view:

report = pd.pivot_table(
    df,
    values="Math",
    index="Department",
    columns="Gender",
    aggfunc="mean"
)

print(report)

This makes it easier to compare groups across two dimensions.

40. Practical Example — Combining Student Information

students = pd.DataFrame({
    "StudentID": [101, 102, 103],
    "Name": [
        "Alice",
        "Brian",
        "Clara"
    ]
})

attendance = pd.DataFrame({
    "StudentID": [101, 102, 103],
    "Attendance": [
        95,
        88,
        97
    ]
})

student_report = pd.merge(
    students,
    attendance,
    on="StudentID",
    how="left"
)

print(student_report)

The result combines student identity information with attendance information.

41. Practical Example — Combining Monthly Data

january = pd.DataFrame({
    "Month": ["January", "January"],
    "Sales": [1200, 1500]
})

february = pd.DataFrame({
    "Month": ["February", "February"],
    "Sales": [1400, 1700]
})

sales = pd.concat(
    [january, february],
    ignore_index=True
)

print(sales)

This is appropriate when the DataFrames have the same structure and represent records that should be stacked.

42. Complete Transformation Pipeline

A realistic workflow may combine cleaning, transformation, aggregation, and merging.

import pandas as pd

# Load data
students = pd.read_csv(
    "students.csv"
)

scores = pd.read_csv(
    "scores.csv"
)

# Remove duplicate student records
students = students.drop_duplicates(
    subset=["StudentID"]
)

# Clean names
students["Name"] = (
    students["Name"]
    .str.strip()
    .str.title()
)

# Combine student information with scores
report = pd.merge(
    students,
    scores,
    on="StudentID",
    how="left"
)

# Create total score
report["Total"] = (
    report["Math"] +
    report["Science"]
)

# Department summary
summary = report.groupby(
    "Department",
    as_index=False
).agg(
    average_total=("Total", "mean"),
    highest_total=("Total", "max"),
    student_count=("StudentID", "count")
)

print(summary)

This demonstrates how individual pandas techniques can be combined into a practical analytical pipeline.

43. Grouped Analysis with filter()

In some situations, you may want to retain only groups satisfying a condition.

result = df.groupby(
    "Department"
).filter(
    lambda group: len(group) >= 2
)

This retains groups containing at least two records.

Advanced Concept:

filter() operates on groups rather than individual rows and can be useful when the decision depends on properties of an entire group.

44. Group-Based Transformation with transform()

transform() is useful when you want a group-level calculation returned with the same row structure as the original DataFrame.

df["DepartmentAverage"] = (
    df.groupby("Department")["Math"]
    .transform("mean")
)

Each student receives the average Mathematics score of their own department.

Important Difference:

groupby().mean() generally reduces the data to group-level results, while groupby().transform() returns values aligned with the original rows.

45. Sorting Aggregated Results

After aggregation, you may want to rank groups.

summary = (
    df.groupby(
        "Department",
        as_index=False
    )["Math"]
    .mean()
    .sort_values(
        "Math",
        ascending=False
    )
)

This places the department with the highest average Mathematics score first.

46. Validating Merges

When combining datasets, unexpected duplicate keys or missing matches can create incorrect results.

For important workflows, pandas provides the validate parameter.

result = pd.merge(
    students,
    scores,
    on="StudentID",
    how="left",
    validate="one_to_one"
)

This can help verify that the relationship between the datasets matches the expected structure.

Real-World Tip:

Before merging, understand whether your key is unique. A many-to-many merge can unexpectedly multiply rows.

47. Beware of Row Multiplication in Merges

Suppose the same key occurs multiple times in both DataFrames. An ordinary merge can produce multiple combinations of matching records.

Therefore, always inspect:

df["StudentID"].duplicated().sum()

before assuming that StudentID uniquely identifies records.

Golden Merge Rule:

Know the relationship between your keys before joining datasets.

48. Pandas Transformation Interview Questions

Q1. What is the purpose of groupby()?

View Answer

groupby() divides data into groups based on one or more columns so that calculations or transformations can be performed separately for each group.

Q2. What are the three conceptual steps of groupby processing?

View Answer

Split → Apply → Combine.

Q3. What is the difference between merge() and concat()?

View Answer

merge() combines records based on matching keys, whereas concat() combines pandas objects along an axis, commonly by stacking rows or columns.

Q4. What is the difference between merge() and join()?

View Answer

merge() is designed for key-based relational combinations and can use ordinary columns, while join() is particularly convenient for combining DataFrames using indexes.

Q5. What does how="inner" mean in a merge?

View Answer

It retains only rows whose merge keys have matching values in both DataFrames.

Q6. What is a pivot table?

View Answer

A pivot table is a summarized and reshaped representation of data that aggregates values across one or more dimensions.

Q7. What is the purpose of as_index=False?

View Answer

It keeps the grouping columns as regular DataFrame columns rather than making them the index of the grouped result.

Q8. Why can a merge unexpectedly increase the number of rows?

View Answer

If the merge key occurs multiple times on both sides, the matching combinations can multiply, producing more rows than either original DataFrame.

49. Examination Questions

Multiple Choice Questions

Q1. Which pandas method is primarily used to group data?

  1. group()
  2. groupby()
  3. group_data()
  4. categorize()

Answer: B — groupby()

Q2. Which function creates a pivot table in pandas?

  1. pd.pivot()
  2. pd.table()
  3. pd.pivot_table()
  4. pd.reshape_table()

Answer: C — pd.pivot_table()

Q3. Which method is commonly used for database-style key-based combinations?

  1. merge()
  2. append()
  3. stack()
  4. combine_rows()

Answer: A — merge()

Q4. Which function is commonly used to stack DataFrames vertically?

  1. pd.merge()
  2. pd.concat()
  3. pd.join()
  4. pd.stack_rows()

Answer: B — pd.concat()

Q5. What does axis=0 usually represent when concatenating DataFrames?

  1. Horizontal combination
  2. Vertical combination
  3. Index deletion
  4. Column sorting

Answer: B — Vertical combination

Q6. Which merge type retains only matching keys?

  1. left
  2. right
  3. outer
  4. inner

Answer: D — inner

Q7. Which parameter keeps grouping columns as ordinary columns?

  1. keep_columns=True
  2. as_index=False
  3. index=False
  4. columns=True

Answer: B — as_index=False

Q8. Which method is particularly convenient for index-based DataFrame combinations?

  1. join()
  2. pivot()
  3. aggregate()
  4. groupby()

Answer: A — join()

50. Practical Examination Questions

Question 1 — Grouped Average

Find the average Mathematics score for each department.

Solution:

result = df.groupby(
    "Department"
)["Math"].mean()

print(result)

Question 2 — Multiple Aggregations

Find the minimum, maximum, and average Science score for each department.

Solution:

result = df.groupby(
    "Department"
)["Science"].agg(
    ["min", "max", "mean"]
)

print(result)

Question 3 — Pivot Table

Create a pivot table showing average Mathematics scores by department and gender.

Solution:

result = pd.pivot_table(
    df,
    values="Math",
    index="Department",
    columns="Gender",
    aggfunc="mean"
)

print(result)

Question 4 — Merge

Combine two DataFrames using StudentID.

Solution:

result = pd.merge(
    students,
    scores,
    on="StudentID",
    how="inner"
)

Question 5 — Concatenation

Combine January and February DataFrames vertically and create a new sequential index.

Solution:

result = pd.concat(
    [january, february],
    ignore_index=True
)

51. Scenario-Based Questions

Scenario 1: A company has separate DataFrames for January, February, and March transactions. All three DataFrames have the same columns.

Which technique is most appropriate?

Answer: pd.concat(), because the datasets have compatible structures and need to be stacked.

Scenario 2: A customer table contains customer details and an order table contains orders. Both contain CustomerID.

Which technique is appropriate?

Answer: merge(), because the tables need to be combined using a common key.

Scenario 3: A report needs the average sales for every region.

Answer: groupby() with mean().

Scenario 4: A report needs sales summarized by region in rows and product category in columns.

Answer: pivot_table().

52. Common Mistakes

  1. Confusing merge and concat: merge() matches records using keys; concat() combines objects along an axis.
  2. Forgetting the merge key: Always identify the column or index that establishes the relationship between datasets.
  3. Ignoring duplicate keys: Duplicate keys can cause a merge to produce more rows than expected.
  4. Using the wrong join type: Choosing inner instead of left, for example, can unintentionally remove records.
  5. Forgetting ignore_index=True: Vertically concatenated DataFrames may otherwise retain their original indexes.
  6. Misunderstanding groupby output: Grouping columns may become indexes unless as_index=False or reset_index() is used.
  7. Replacing missing pivot combinations blindly: A displayed zero does not necessarily mean the original data contained zero.

53. Which Pandas Method Should You Use?

Your Requirement Recommended Method
Calculate average by category groupby()
Calculate multiple statistics by category groupby().agg()
Create a cross-tabulated summary pivot_table()
Combine tables using a common key merge()
Combine tables using indexes join()
Stack datasets vertically concat(axis=0)
Combine columns horizontally concat(axis=1)
Add group-level values back to every original row groupby().transform()

54. Quick Revision Cheatsheet

Command Purpose
df.groupby("A") Group by column A.
df.groupby("A")["B"].mean() Average B by A.
df.groupby("A")["B"].sum() Sum B by A.
df.groupby("A")["B"].agg(["min","max","mean"]) Multiple statistics.
df.groupby("A", as_index=False) Keep A as a regular column.
df.groupby("A")["B"].transform("mean") Group average aligned to original rows.
pd.pivot_table() Create summarized pivot table.
pd.merge() Combine DataFrames using keys.
how="inner" Keep matching keys.
how="left" Keep all left records.
how="right" Keep all right records.
how="outer" Keep all keys.
df.join(other) Index-oriented combination.
pd.concat([df1, df2]) Combine vertically.
pd.concat([df1, df2], axis=1) Combine horizontally.
ignore_index=True Create a fresh sequential index.
reset_index() Convert index levels into columns.

55. Key Takeaways

  • groupby() is fundamental for grouped analysis.
  • Grouped analysis follows the Split → Apply → Combine concept.
  • agg() allows multiple statistics to be calculated efficiently.
  • pivot_table() creates multidimensional summary tables.
  • merge() combines DataFrames using matching keys.
  • join() is particularly convenient for index-based combinations.
  • concat() combines DataFrames along rows or columns.
  • as_index=False keeps group labels as normal columns.
  • transform() can add group-level calculations back to the original rows.
  • Always inspect keys and relationships before merging DataFrames.
Golden Rule:

Group when you need summaries, pivot when you need a multidimensional report, merge when you need key-based relationships, join when indexes matter, and concatenate when datasets need to be stacked or aligned.