Analyze Sales Data Like an Analyst

Load, clean, and visualize messy sales data to surface business insights.

Introduction

30 Second Summary

Every business decision starts with a question. Which product brings in the most money? Are sales going up or down? Which region is underperforming? The people who answer these questions with data are the ones who get listened to.

In this project, you will analyze a sales dataset using Python to answer real stakeholder questions. You will clean messy data, calculate business metrics, and create charts that communicate insights at a glance.

What You'll Build

A complete data analysis notebook that transforms raw sales data into actionable business insights using pandas and matplotlib.

By the end of this project, you'll have:

  • A Google Colab notebook that loads, cleans, and analyzes a sales dataset end-to-end.
  • A cleaned dataset with engineered features like Revenue and Month columns that make analysis possible.
  • Charts (bar chart and line chart) that communicate business insights visually to stakeholders.
  • Secret Mission: Calculate month-over-month growth rates and build a product-by-region revenue heatmap.

Are there any prerequisites?

You only need a Google account (Gmail, Google Drive, etc.). No Python or coding experience is required. Google Colab runs entirely in your browser, so there is nothing to install.

Set Up Your Analysis Workspace

Every data analysis project starts with the same question: where do I actually write and run my code? You need an environment that lets you type Python, run it, and see results all in one place.

In this step, you will open Google Colab, a free browser-based notebook tool that requires zero installation. You will also import your Python libraries and generate a sample sales dataset to analyze throughout this project.

In this step, get ready to:
  • Open Google Colab and create a new notebook.
  • Import pandas, numpy, and matplotlib.
  • Generate a realistic sales dataset with intentional data quality issues.
Open Google Colab and create a new notebook

Google Colab is a free notebook tool from Google that runs entirely in your browser. You type code into cells, click a play button, and see results immediately below.

  • Open a new browser tab and navigate to colab.research.google.com.
  • Sign in with your Google account if prompted.
  • Click New notebook to create a fresh notebook.
  • Click on the name Untitled0.ipynb at the top left of the page.
  • Type Sales_Analysis and press Enter to rename your notebook.

What is Google Colab?

Google Colab is a free, cloud-based Jupyter notebook environment. It gives you a Python environment with popular data science libraries pre-installed, so you never need to set up anything on your own computer.

A notebook is made up of cells. You type code into a cell, run it, and the output appears directly below. This makes notebooks perfect for data analysis because you can see your results at every stage.

You should now see an empty notebook with one code cell ready for input. The notebook name in the top left reads Sales_Analysis.ipynb.

Don't see New notebook?

Make sure you are signed in to your Google account. Some organizational accounts may restrict access to Colab. Try using a personal Gmail account instead.

Help me get Google Colab open in my browser.

Verify your environment and import libraries

Before you start analyzing data, you need to load three Python libraries that handle different parts of the workflow. These libraries come pre-installed in Colab, so you just need to import them.

  • Import your analysis libraries by typing this code into the first code cell:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

print(f"pandas version: {pd.__version__}")
print("Setup complete! You are ready to analyze data.")

What does this code do?

  • pandas (imported as pd) is the core library for working with tables of data. Think of it as Excel inside Python.
  • numpy (imported as np) handles math operations and generating random numbers.
  • matplotlib.pyplot (imported as plt) creates charts and visualizations from your data.
  • The two print() lines confirm that everything loaded correctly by showing the pandas version number.
  • Click the play button on the left side of the cell to run it. You can also press Shift+Enter.
  • You should see pandas version: 2.2.2 and Setup complete! You are ready to analyze data. printed below the cell.

Seeing a NameError or ModuleNotFoundError?

Check that you typed the import statements exactly as shown. A common mistake is misspelling matplotlib or forgetting the .pyplot part.

If you see a connection error, click Connect in the top right corner of the notebook to connect to a runtime, then try running the cell again.

Help me fix this import error in my Colab notebook.

Generate the sample sales dataset

In a real job, you would receive a data file from a database export or a colleague. Here, you will generate a realistic sales dataset so you always have data to work with regardless of when you do this project.

The dataset simulates 6 months of sales across 5 products and 4 regions. It also has intentional messiness (some missing values and a few duplicate rows) so you can practice real data cleaning techniques later.

  • Click the + Code button in the toolbar to add a new code cell below Cell 1.
  • Generate your sales dataset by pasting this code into the new cell and running it:
np.random.seed(42)

n_rows = 200
products = ['Laptop', 'Headphones', 'Keyboard', 'Monitor', 'Mouse']
regions = ['North', 'South', 'East', 'West']
prices = {'Laptop': 999.99, 'Headphones': 79.99, 'Keyboard': 49.99, 'Monitor': 349.99, 'Mouse': 29.99}

dates = pd.date_range(start='2025-01-01', end='2025-06-30', periods=n_rows)
product_choices = np.random.choice(products, n_rows)
region_choices = np.random.choice(regions, n_rows)
quantities = np.random.randint(1, 10, n_rows)
unit_prices = [prices[p] for p in product_choices]

df_raw = pd.DataFrame({
    'OrderDate': dates.strftime('%Y-%m-%d'),
    'Product': product_choices,
    'Region': region_choices,
    'Quantity': quantities,
    'UnitPrice': unit_prices
})

# Introduce messiness: missing values and duplicates
df_raw.loc[5, 'UnitPrice'] = np.nan
df_raw.loc[12, 'UnitPrice'] = np.nan
df_raw.loc[20, 'UnitPrice'] = np.nan
df_raw.loc[8, 'Quantity'] = np.nan
df_raw.loc[15, 'Quantity'] = np.nan
df_raw = pd.concat([df_raw, df_raw.iloc[[3, 7, 50]]], ignore_index=True)

df_raw.to_csv('sales_data.csv', index=False)
print("Sales dataset created and saved as 'sales_data.csv'")
print(f"Shape: {df_raw.shape[0]} rows, {df_raw.shape[1]} columns")

What does this code do?

  • np.random.seed(42) locks in the random number generator so you get the same dataset every time you run the code.
  • The code creates 200 rows of order data with 5 columns: OrderDate, Product, Region, Quantity, and UnitPrice.
  • It then deliberately introduces 3 missing UnitPrice values, 2 missing Quantity values, and 3 duplicate rows. This simulates the messiness you find in real-world data.
  • Finally, it saves everything to a CSV file called sales_data.csv. This simulates receiving a data file from a colleague or database export.
  • Run the cell by pressing Shift+Enter.
  • You should see Sales dataset created and saved as 'sales_data.csv' and Shape: 203 rows, 5 columns printed below the cell.

Why 203 rows instead of 200?

The code generated 200 original rows, then appended 3 duplicate rows on purpose. This gives you 203 total rows. You will remove those duplicates in the cleaning step later.

Seeing a NameError for np or pd?

This means Cell 1 (your imports) has not been run yet. Go back to Cell 1 and click the play button to run it first, then run Cell 2 again.

Cells in a notebook must be run in order. If you restart the notebook at any point, re-run all cells from the top.

Help me fix this NameError in my Colab notebook.

✔️ Awesome, I've got everything!

Great. Double check that both cells ran without errors and that your notebook is saved as Sales_Analysis.ipynb.

ⓧ I'd like to double check the full code

Here is the full code for both cells. Compare yours against these:

Cell 1:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

print(f"pandas version: {pd.__version__}")
print("Setup complete! You are ready to analyze data.")

Cell 2:

np.random.seed(42)

n_rows = 200
products = ['Laptop', 'Headphones', 'Keyboard', 'Monitor', 'Mouse']
regions = ['North', 'South', 'East', 'West']
prices = {'Laptop': 999.99, 'Headphones': 79.99, 'Keyboard': 49.99, 'Monitor': 349.99, 'Mouse': 29.99}

dates = pd.date_range(start='2025-01-01', end='2025-06-30', periods=n_rows)
product_choices = np.random.choice(products, n_rows)
region_choices = np.random.choice(regions, n_rows)
quantities = np.random.randint(1, 10, n_rows)
unit_prices = [prices[p] for p in product_choices]

df_raw = pd.DataFrame({
    'OrderDate': dates.strftime('%Y-%m-%d'),
    'Product': product_choices,
    'Region': region_choices,
    'Quantity': quantities,
    'UnitPrice': unit_prices
})

# Introduce messiness: missing values and duplicates
df_raw.loc[5, 'UnitPrice'] = np.nan
df_raw.loc[12, 'UnitPrice'] = np.nan
df_raw.loc[20, 'UnitPrice'] = np.nan
df_raw.loc[8, 'Quantity'] = np.nan
df_raw.loc[15, 'Quantity'] = np.nan
df_raw = pd.concat([df_raw, df_raw.iloc[[3, 7, 50]]], ignore_index=True)

df_raw.to_csv('sales_data.csv', index=False)
print("Sales dataset created and saved as 'sales_data.csv'")
print(f"Shape: {df_raw.shape[0]} rows, {df_raw.shape[1]} columns")

Your analysis workspace is ready and loaded with data. Next up, you will load that CSV file back into Python and start exploring what is inside.

Load and Explore the Data

Your sales dataset is saved and ready to go. Now it is time to do what every business analyst does first: open the data and look at it.

Before running any calculations or building charts, you need to understand what you are working with. How many rows are there? What do the columns look like? Is anything missing or broken? These questions shape every decision you make next.

In this step, get ready to:
  • Load your sales CSV file into a DataFrame for analysis.
  • Explore the data's structure using built-in pandas methods.
  • Identify data quality issues that need fixing before analysis.
Load the CSV and take a first look

The very first thing a business analyst does with any dataset is load it and look at the raw rows. This tells you what columns exist, what the values look like, and whether anything seems off at first glance.

  • Click the + Code button at the top of your notebook to create a new code cell.
  • Load and explore your dataset by typing this code into the new cell:
df = pd.read_csv('sales_data.csv')

print("=== First 5 Rows ===")
print(df.head())
print()
print("=== Shape ===")
print(f"{df.shape[0]} rows, {df.shape[1]} columns")
print()
print("=== Data Types and Missing Values ===")
print(df.info())
print()
print("=== Summary Statistics ===")
print(df.describe())

What does this code do?

  • pd.read_csv('sales_data.csv') reads the CSV file into a DataFrame called df. A DataFrame is a table of rows and columns that pandas can manipulate.
  • df.head() shows you the first 5 rows so you can see real values without scrolling through hundreds of rows.
  • df.shape tells you the total number of rows and columns in one quick check.
  • df.info() reveals data types for each column and highlights where values are missing.
  • df.describe() calculates summary statistics (mean, min, max) for every numeric column.
  • Run the cell by clicking the play button on the left, or press Shift+Enter.

You should see a table of 5 rows with columns for OrderDate, Product, Region, Quantity, and UnitPrice. Below that, you should see the shape reported as 203 rows and 5 columns.

Seeing a FileNotFoundError?

This means sales_data.csv does not exist yet. Go back to Cell 2 (the dataset generation cell) and run it first. The file only exists after that cell executes successfully.

If you disconnected and reconnected your Colab runtime, you need to re-run Cell 2 because files are cleared on disconnect.

Why can't Colab find my sales_data.csv file?

Understand the shape and structure

Now that the cell has run, look at the output sections below it. Each one tells you something different about your data.

  • Look at the First 5 Rows section. Each row is one sales order with a date, product name, region, quantity sold, and unit price.
  • Look at the Shape section. You have 203 rows (one per order) and 5 columns.
  • Look at the Data Types and Missing Values section. Notice that some columns show fewer than 203 "non-null" entries. That means values are missing.
  • Look at the Summary Statistics section. The min and max values tell you the range. The mean tells you the average.

Why look at data before analyzing it?

Business analysts never jump straight into calculations. If you calculate revenue on data that has missing quantities or duplicate rows, your numbers will be wrong. Every decision made from those numbers would be flawed.

This "load, look, understand" pattern is your first step in every single analysis project.

Identify data quality issues

You spotted hints of missing data in the df.info() output. Now you need exact counts of what is broken. This tells you exactly how much cleanup work lies ahead.

  • Click the + Code button to create another new code cell below Cell 3.
  • Check for missing values and duplicate rows by typing this code:
print("=== Missing Values ===")
print(df.isnull().sum())
print()
print("=== Duplicate Rows ===")
print(f"Number of duplicates: {df.duplicated().sum()}")

What does this code do?

  • df.isnull().sum() counts how many missing values exist in each column. This pinpoints exactly where the gaps are.
  • df.duplicated().sum() counts rows that appear more than once. Duplicates inflate your totals and skew averages.
  • Run the cell by clicking the play button or pressing Shift+Enter.

You should see that UnitPrice has 3 missing values, Quantity has 2 missing values, and there are 3 duplicate rows. These are the exact problems you will fix in the next step.

Seeing a NameError for df?

This means Cell 3 (where you created df) has not been run yet. Colab cells must run in order. Go back and run Cell 3 first, then run Cell 4 again.

Why is Colab saying df is not defined?

The business analyst mindset

Before you analyze, you must understand what is broken. Reporting on dirty data leads to wrong decisions. A stakeholder who sees inflated revenue numbers because of duplicate rows will make bad budget calls.

You now know exactly what needs fixing: 5 missing values across two columns and 3 duplicate rows. That is your cleaning checklist for the next step.

✔️ Awesome, I've got everything!

Great. Make sure both Cell 3 and Cell 4 ran without errors before moving on.

ⓧ I'd like to double check the full code

Here is Cell 3 (Load and Explore):

df = pd.read_csv('sales_data.csv')

print("=== First 5 Rows ===")
print(df.head())
print()
print("=== Shape ===")
print(f"{df.shape[0]} rows, {df.shape[1]} columns")
print()
print("=== Data Types and Missing Values ===")
print(df.info())
print()
print("=== Summary Statistics ===")
print(df.describe())

Here is Cell 4 (Check Data Quality):

print("=== Missing Values ===")
print(df.isnull().sum())
print()
print("=== Duplicate Rows ===")
print(f"Number of duplicates: {df.duplicated().sum()}")

You have diagnosed the problems in your dataset. Next up, you will clean this data by handling the missing values, removing duplicates, and engineering new columns that unlock deeper analysis.

Clean the Data

In Step 2 you explored the dataset and uncovered its problems: missing values and duplicate rows. Now it is time to fix those problems so your analysis produces trustworthy results.

Cleaning data means removing rows that should not be there and filling gaps where information is missing. You will also create new columns that your stakeholders actually care about, like Revenue.

In this step, get ready to:
  • Remove duplicates and handle missing values in the dataset.
  • Convert date strings to proper datetime objects.
  • Create Revenue and Month columns through feature engineering.
Handle missing values and duplicates

Reporting on dirty data leads to wrong decisions. Before any analysis, a business analyst removes rows that should not exist and fills gaps where values are missing.

  • Create a new code cell below Cell 4 and type the following cleaning code:
# Remove duplicates
df = df.drop_duplicates()
print(f"After removing duplicates: {df.shape[0]} rows")

# Fill missing UnitPrice with median
median_price = df['UnitPrice'].median()
df['UnitPrice'] = df['UnitPrice'].fillna(median_price)
print(f"Filled missing UnitPrice with median: ${median_price:.2f}")

# Drop rows where Quantity is missing
df = df.dropna(subset=['Quantity'])
print(f"After dropping missing Quantity: {df.shape[0]} rows")

What does this code do?

Each section tackles one of the data quality issues you identified in the previous step.

  • `df.drop_duplicates()` removes the 3 exact-copy rows so they do not inflate your totals.
  • `df['UnitPrice'].median()` calculates the middle value of all existing prices. The median is safer than the mean because one expensive product (like a $999 Laptop) would skew the mean upward.
  • `df.dropna(subset=['Quantity'])` drops only the rows where Quantity is missing. You cannot calculate revenue without knowing how many items were sold.

You will not run this cell yet. In the next substep you will add more code to this same cell before running it.

Fix data types and create new columns

Feature engineering means creating new columns that make analysis easier. Revenue does not exist in the raw data, but it is the KPI your stakeholders care about most.

  • Below the code you just typed in the same cell, add the following:
# Convert OrderDate to datetime
df['OrderDate'] = pd.to_datetime(df['OrderDate'])

# Create Revenue and Month columns
df['Revenue'] = df['Quantity'] * df['UnitPrice']
df['Month'] = df['OrderDate'].dt.month

print()
print("=== Cleaned Data Sample ===")
print(df.head())
print()
print("=== Confirm No Missing Values ===")
print(df.isnull().sum())

What does this code do?

`pd.to_datetime()` converts the OrderDate strings into real date objects. This lets pandas understand them as dates instead of plain text.

`df['Revenue'] = df['Quantity'] * df['UnitPrice']` multiplies quantity by price for each row to create the Revenue column. `df['OrderDate'].dt.month` extracts the month number (1 through 6) so you can group sales by month later.

  • Run the cell by clicking the play button or pressing Shift+Enter.
  • You should see three confirmation messages showing the row count after each cleaning operation, followed by a table with the new Revenue and Month columns, and a final list confirming 0 missing values for every column.

Seeing a NameError for df?

This means Cell 3 (where you loaded the CSV into df) has not been run yet. Click on Cell 3 and run it first, then re-run Cell 5.

If you see a KeyError mentioning a column name, double check that your column names match exactly: UnitPrice, Quantity, OrderDate. Column names are case-sensitive.

help me fix my data cleaning cell

✔️ Awesome, I've got everything!

Great. Make sure the output shows 0 for every column under Confirm No Missing Values.

ⓧ I'd like to double check the full code

Here is the complete Cell 5. Make sure your cell matches this exactly:

# Remove duplicates
df = df.drop_duplicates()
print(f"After removing duplicates: {df.shape[0]} rows")

# Fill missing UnitPrice with median
median_price = df['UnitPrice'].median()
df['UnitPrice'] = df['UnitPrice'].fillna(median_price)
print(f"Filled missing UnitPrice with median: ${median_price:.2f}")

# Drop rows where Quantity is missing
df = df.dropna(subset=['Quantity'])
print(f"After dropping missing Quantity: {df.shape[0]} rows")

# Convert OrderDate to datetime
df['OrderDate'] = pd.to_datetime(df['OrderDate'])

# Create Revenue and Month columns
df['Revenue'] = df['Quantity'] * df['UnitPrice']
df['Month'] = df['OrderDate'].dt.month

print()
print("=== Cleaned Data Sample ===")
print(df.head())
print()
print("=== Confirm No Missing Values ===")
print(df.isnull().sum())

Your data is now clean, complete, and ready for analysis. Next up, you will use groupby to calculate real business KPIs like revenue by product and monthly sales trends.

Analyze Business KPIs

Your data is clean and ready. Now comes the part that actually matters to the business: answering questions with numbers.

In this step, you will use pandas groupby to split your data into meaningful groups and calculate KPIs like total revenue by product, monthly trends, and regional performance.

In this step, get ready to:
  • Calculate total revenue by product to identify your top earner.
  • Analyze monthly sales trends to spot growth or decline.
  • Compare regional performance using multiple metrics at once.
Calculate revenue by product

The first question a stakeholder always asks is: "Which product drives the most revenue?" The groupby operation answers this by splitting your data into groups, applying a calculation to each group, and combining the results.

  • Click the + Code button in the toolbar to create a new code cell.
  • Type the following code to group revenue by product and sort from highest to lowest:
revenue_by_product = df.groupby('Product')['Revenue'].sum().sort_values(ascending=False)

print("=== Revenue by Product ===")
for product, revenue in revenue_by_product.items():
    print(f"  {product}: ${revenue:,.2f}")
print(f"\n  Total: ${revenue_by_product.sum():,.2f}")

What does this code do?

The groupby('Product') splits your DataFrame into separate groups, one for each product (Laptop, Headphones, Keyboard, Monitor, Mouse).

Then ['Revenue'].sum() adds up all revenue within each group. Finally, .sort_values(ascending=False) puts the highest-revenue product first.

  • Run the cell by clicking the play button or pressing Shift+Enter.
  • You should see a ranked list of all 5 products with dollar amounts, and Laptop at the top since it has the highest unit price.

Why is groupby the most important pandas operation for a business analyst?

It splits your data into groups, applies a calculation to each group, and combines the results. Almost every business question ("revenue by product", "orders by region", "trends by month") follows this exact pattern.

Seeing a KeyError?

Make sure you ran Cell 5 first. The Revenue column only exists after the cleaning step creates it.

Also check that the column name is capitalized exactly as Revenue and Product. Column names are case-sensitive.

Why is my groupby returning a KeyError?

Calculate monthly sales trends

Stakeholders need to know if revenue is growing or shrinking over time. Monthly trends reveal seasonal patterns and help forecast future performance.

  • Create a new code cell and type the following to calculate revenue and order count by month:
monthly_revenue = df.groupby('Month')['Revenue'].sum()
monthly_orders = df.groupby('Month')['Revenue'].count()

print("=== Monthly Revenue ===")
for month, revenue in monthly_revenue.items():
    print(f"  Month {month}: ${revenue:,.2f} ({monthly_orders[month]} orders)")

What does this code do?

The first groupby('Month') groups all orders by their month number (1 through 6) and sums the revenue in each group. The second groupby counts how many orders occurred each month.

Together, these two numbers tell a stakeholder both how much money came in and how many transactions drove that number. A month with high revenue but few orders might mean big-ticket sales.

  • Run the cell.
  • You should see each month listed with its total revenue and order count in parentheses.

Seeing all zeros or unexpected values?

The Month column is created in Cell 5 when you convert OrderDate to datetime. If Cell 5 was not run successfully, the Month column will not exist.

Go back and run all cells from Cell 5 onward using Runtime > Run after.

Help me troubleshoot my monthly revenue calculation.

Compare regional performance

Sometimes you need more than one number per group. The .agg() method lets you request multiple calculations in a single line.

  • Create a new code cell and type the following to calculate total revenue, average order value, and order count for each region:
regional_performance = df.groupby('Region').agg(
    TotalRevenue=('Revenue', 'sum'),
    AvgOrderValue=('Revenue', 'mean'),
    OrderCount=('Revenue', 'count')
).round(2)

regional_performance = regional_performance.sort_values('TotalRevenue', ascending=False)

print("=== Regional Performance ===")
print(regional_performance)

What does this code do?

The .agg() method uses named aggregation syntax: TotalRevenue=('Revenue', 'sum') means "create a column called TotalRevenue by summing the Revenue column." Each line defines a new metric.

This is the modern pandas 2.2 recommended pattern. It produces clean, single-level column names without confusing MultiIndex complications.

  • Run the cell.
  • You should see a table with four rows (North, South, East, West) and three columns (TotalRevenue, AvgOrderValue, OrderCount), sorted with the highest-revenue region at the top.

Getting a TypeError on .agg()?

The named aggregation syntax requires parentheses around each tuple: TotalRevenue=('Revenue', 'sum'). Missing the parentheses or using curly braces instead will cause an error.

Double-check that each line inside .agg() follows the pattern NewName=('column', 'function').

Help me fix my .agg() syntax error.

✔️ Awesome, I've got everything!

Great. Double check that all three cells (6, 7, and 8) ran without errors and produced output.

ⓧ I'd like to double check the full code

Here are all three cells you should have for this step:

Cell 6:

revenue_by_product = df.groupby('Product')['Revenue'].sum().sort_values(ascending=False)

print("=== Revenue by Product ===")
for product, revenue in revenue_by_product.items():
    print(f"  {product}: ${revenue:,.2f}")
print(f"\n  Total: ${revenue_by_product.sum():,.2f}")

Cell 7:

monthly_revenue = df.groupby('Month')['Revenue'].sum()
monthly_orders = df.groupby('Month')['Revenue'].count()

print("=== Monthly Revenue ===")
for month, revenue in monthly_revenue.items():
    print(f"  Month {month}: ${revenue:,.2f} ({monthly_orders[month]} orders)")

Cell 8:

regional_performance = df.groupby('Region').agg(
    TotalRevenue=('Revenue', 'sum'),
    AvgOrderValue=('Revenue', 'mean'),
    OrderCount=('Revenue', 'count')
).round(2)

regional_performance = regional_performance.sort_values('TotalRevenue', ascending=False)

print("=== Regional Performance ===")
print(regional_performance)

You now have three key business insights sitting in your notebook: product rankings, monthly trends, and regional performance. Next up, you will turn these numbers into charts that communicate findings at a glance.

Visualize Insights

You now have all the numbers a stakeholder would want: revenue by product, monthly trends, and regional comparisons.

Numbers in a table are accurate, but charts communicate findings faster. A stakeholder glances at a bar chart and immediately knows which product matters most. In this step, you will turn your analysis into visual charts using matplotlib.

In this step, get ready to:
  • Create a horizontal bar chart showing revenue by product.
  • Create a line chart showing monthly revenue trends.
  • Display the regional summary with a plain-English key finding.
Create a revenue-by-product bar chart

A horizontal bar chart is ideal for comparing categories like products. The longest bar jumps out instantly, making it obvious which product drives the most revenue.

  • Click the + Code button at the top of your notebook to add a new code cell.
  • Type the following code into the new cell:
plt.figure(figsize=(10, 6))
revenue_by_product.plot(kind='barh', color='steelblue')
plt.title('Revenue by Product', fontsize=14)
plt.xlabel('Revenue ($)')
plt.ylabel('Product')
plt.tight_layout()
plt.show()

What does this code do?

  • plt.figure(figsize=(10, 6)) creates a blank canvas 10 inches wide by 6 inches tall.
  • .plot(kind='barh') tells pandas to draw a horizontal bar chart directly from the revenue_by_product Series you calculated in the previous step.
  • plt.tight_layout() adjusts spacing so labels do not get cut off at the edges.
  • Run the cell by clicking the play button or pressing Shift+Enter.
  • You should see a horizontal bar chart with product names on the left and blue bars extending to the right showing each product's revenue.

Don't see a chart?

  • Make sure you ran all previous cells in order (Cell 1 through Cell 8). The revenue_by_product variable must exist before this cell can use it.
  • Check for a NameError in the output. This means a prior cell was skipped. Click Runtime > Run all to re-run everything from the top.

Why is my bar chart not appearing in Colab?

Create a monthly revenue trend line chart

Line charts show change over time. If revenue is trending up, the business is healthy. If it is flat or declining, that is a signal to investigate.

  • Add another new code cell by clicking + Code.
  • Type this code to plot monthly revenue as a line chart with markers:
plt.figure(figsize=(10, 6))
plt.plot(monthly_revenue.index, monthly_revenue.values, marker='o', linewidth=2, color='steelblue')
plt.title('Monthly Revenue Trend (Jan-Jun 2025)', fontsize=14)
plt.xlabel('Month')
plt.ylabel('Revenue ($)')
plt.xticks(monthly_revenue.index, ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'])
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

What does this code do?

  • plt.plot() draws a line connecting data points. The marker='o' argument adds a visible dot at each month so individual values are easy to read.
  • plt.xticks() replaces the raw month numbers (1, 2, 3...) with readable names (Jan, Feb, Mar...) on the x-axis.
  • plt.grid(True, alpha=0.3) adds faint gridlines behind the chart so the eye can follow values across to the y-axis.
  • Run the cell.
  • You should see a line chart with dots at each month (Jan through Jun) and faint gridlines in the background.

Seeing numbers instead of month names on the x-axis?

Double-check that your plt.xticks() line uses monthly_revenue.index as the first argument and the list of month name strings as the second.

Why are my x-axis labels wrong on my matplotlib line chart?

Display the regional summary as a formatted table

Every analysis should end with a plain-English finding. Numbers without interpretation are useless to a stakeholder.

  • Add one more code cell by clicking + Code.
  • Type this code to print a key finding followed by the regional performance table:
print("KEY FINDING:")
top_region = regional_performance.index[0]
top_revenue = regional_performance.iloc[0]['TotalRevenue']
print(f"  {top_region} region leads with ${top_revenue:,.2f} in total revenue.")
print()
print(regional_performance.to_string())

What does this code do?

  • regional_performance.index[0] grabs the name of the first row. Because you sorted by TotalRevenue in descending order earlier, the first row is the top-performing region.
  • .iloc[0]['TotalRevenue'] pulls the dollar value from that same top row.
  • .to_string() prints the DataFrame with clean alignment so all columns line up neatly in the output.
  • Run the cell.
  • You should see a KEY FINDING: line naming the top region and its revenue total, followed by the full table of all four regions.

Seeing a KeyError?

Make sure the column name in your code is exactly 'TotalRevenue' with a capital T and capital R. This must match the named aggregation you defined in Cell 8.

Why am I getting a KeyError when accessing my regional_performance DataFrame?

Why end with a plain-English finding?

In a real analyst role, your audience is rarely other analysts. Stakeholders need a one-sentence takeaway they can act on. Pairing a clear finding with the supporting table gives them both the headline and the detail.

✔️ Awesome, I've got everything!

Great. Double check that all three cells (9, 10, and 11) ran without errors and produced visible output.

ⓧ I'd like to double check the full code

Here are all three cells you created in this step. Compare them to your notebook:

Cell 9:

plt.figure(figsize=(10, 6))
revenue_by_product.plot(kind='barh', color='steelblue')
plt.title('Revenue by Product', fontsize=14)
plt.xlabel('Revenue ($)')
plt.ylabel('Product')
plt.tight_layout()
plt.show()

Cell 10:

plt.figure(figsize=(10, 6))
plt.plot(monthly_revenue.index, monthly_revenue.values, marker='o', linewidth=2, color='steelblue')
plt.title('Monthly Revenue Trend (Jan-Jun 2025)', fontsize=14)
plt.xlabel('Month')
plt.ylabel('Revenue ($)')
plt.xticks(monthly_revenue.index, ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'])
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Cell 11:

print("KEY FINDING:")
top_region = regional_performance.index[0]
top_revenue = regional_performance.iloc[0]['TotalRevenue']
print(f"  {top_region} region leads with ${top_revenue:,.2f} in total revenue.")
print()
print(regional_performance.to_string())

Secret mission

Growth Rates and Heatmap

Your analysis so far answers "what happened" but stakeholders also want to know "how fast is it changing?" and "where should we focus?" In this secret mission, you will calculate month-over-month growth rates and build a heatmap that reveals revenue hotspots across products and regions.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in Google Colab, so there are no ongoing costs or cloud resources to worry about. Decide whether to keep your notebook for future reference or delete it from your Google Drive.

Resources you used:

  • Sales_Analysis.ipynb notebook (saved in your Google Drive).
  • sales_data.csv file (stored in Colab's temporary runtime storage).

What about sales_data.csv?

The sales_data.csv file lives in Colab's temporary runtime storage. It disappears automatically when your runtime disconnects (after inactivity or when you close the tab). You do not need to manually delete it.

Keep everything running

No action needed. Choose this if you want to revisit your analysis or build on it later.

  • Your Sales_Analysis.ipynb notebook stays in your Google Drive under the Colab Notebooks folder.
  • You can reopen it anytime at colab.research.google.com by clicking File then Open notebook.
  • The sales_data.csv file will need to be regenerated by re-running Cell 2 the next time you open the notebook.

Pause - I'll come back to this later

Disconnect your Colab runtime to free up resources. Your notebook stays saved in Google Drive.

  • In your Colab notebook, click Runtime in the top menu.
  • Click Disconnect and delete runtime.
  • This frees up the temporary compute session. Your notebook code and outputs remain saved in Google Drive.
  • When you come back, re-run all cells from the top (Runtime then Run all) to regenerate your data and variables.

Delete - I don't want to use this again

Remove your notebook from Google Drive entirely.

  • In your Colab notebook, click File in the top menu.
  • Click Move to trash.
  • To permanently delete it, open drive.google.com, click Trash in the left sidebar, find Sales_Analysis.ipynb, right-click it, and select Delete forever.
  • The sales_data.csv file disappears automatically when the runtime disconnects. No action needed for it.

That's a Wrap!

That's a Wrap!

Nice work! You just went from a messy CSV file to actionable business insights using the same workflow professional analysts use every day.

You've learned how to:

  • Load raw sales data into a pandas DataFrame, identify data quality issues, and clean messy data by handling missing values, removing duplicates, and engineering new features like Revenue and Month.
  • Calculate business KPIs using groupby aggregation to answer stakeholder questions about revenue by product, monthly trends, and regional performance.
  • Create data visualizations (bar chart and line chart) using matplotlib that communicate findings faster than raw numbers.
  • Secret Mission: Calculated month-over-month growth rates and built a product-by-region revenue heatmap using seaborn to spot high-value combinations at a glance.

Ready to quiz yourself?