Analyze Superstore Sales with Python

Uncover which products lose money and why using pandas and matplotlib.

Introduction

30 Second Summary

Every retail business generates mountains of sales data. The real challenge is not collecting it. It is finding the hidden patterns that reveal where money is being made and where it is quietly disappearing.

In this project, you will analyze 9,994 real retail transactions to answer one business question: "Where are we making money, and where are we bleeding it?" You will load, clean, and visualize the data in a Jupyter Notebook using pandas and matplotlib.

What You'll Build

Imagine opening your notebook, running a few cells, and watching charts appear that instantly reveal which products are profitable and which ones destroy profit through excessive discounting.

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

  • A cleaned DataFrame with engineered features like profit margins and time-based columns, built from a real 9,994-row retail dataset.
  • A non-obvious business insight: which product sub-categories lose money despite high sales, and proof that excessive discounting is the cause.
  • A set of publication-quality visualizations (bar charts, scatter plots, and regional comparisons) that communicate a business story at a glance.
  • Secret Mission: A monthly sales trend analysis that reveals seasonal patterns and peak sales periods.

Are there any prerequisites?

You need basic Python knowledge (variables, loops, functions) and Python 3 installed on your computer. You will install Jupyter Notebook as part of Step 1.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters. Every analysis step you take will serve one business question. Naming it now keeps the whole project focused.

Set Up Your Colab Notebook and Load the Dataset

Set Up Your Jupyter Notebook and Load the Dataset

Before you can analyze anything, you need a working environment with data loaded into it. Jupyter Notebook is a local, browser-based tool that lets you write and run Python code in cells. You will install it alongside pandas, matplotlib, and seaborn.

In this step, you will install Jupyter, create a notebook, download a real retail dataset, and load it so you can start exploring immediately.

In this step, get ready to:
  • Install Jupyter Notebook and the required Python libraries.
  • Download the Superstore dataset to your computer.
  • Load the data into a pandas DataFrame and confirm it loaded correctly.
Install Jupyter and create a new notebook
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create a project folder and move into it by running these commands:
mkdir superstore-analysis
cd superstore-analysis
  • Install Jupyter Notebook and the analysis libraries by running this command:
pip install notebook pandas matplotlib seaborn

You should see a list of successfully installed packages in the terminal output.

  • Launch Jupyter Notebook by running this command:
jupyter notebook

Your default browser opens automatically with the Jupyter file browser showing your superstore-analysis folder. Keep the terminal running in the background.

  • In the top-right corner of the Jupyter file browser, click New, then select Python 3.
  • A new notebook opens in a new tab. Click the title Untitled at the top of the page.
  • Rename it to Superstore_Sales_Analysis and click Rename.

Browser didn't open automatically?

  • Look at the terminal output. You should see a URL like http://localhost:8888/?token=.... Copy and paste it into your browser.
  • If you see command not found, the pip install notebook step may not have completed. Run it again and wait for it to finish.

Help me launch Jupyter Notebook

Download the dataset and load it into pandas

The Sample Superstore dataset is a real retail dataset with 9,994 transaction records. You will download it and place it in your project folder so your notebook can read it directly.

  • Open a new browser tab and go to csvbase.com/djkoogy/Sample-Superstore.
  • Click Download as csv to save the file to your computer.
  • Move the downloaded CSV file into your superstore-analysis folder on your Desktop.
  • Switch back to your Jupyter notebook. Click into the first empty code cell and paste the following code:
# =============================================================
# Cell 1: Import Libraries and Load Data
# =============================================================
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Load the CSV file from your project folder
df = pd.read_csv("Sample-Superstore.csv", encoding="latin-1", parse_dates=["Order Date", "Ship Date"])
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

What does this code do?

  • import pandas as pd loads the pandas library for data manipulation. matplotlib and seaborn are for creating charts later.
  • encoding="latin-1" handles special characters in product names that would cause errors with the default encoding.
  • parse_dates=["Order Date", "Ship Date"] converts date columns to proper datetime objects so you can do time-based analysis later.
  • df.head() displays the first 5 rows as a formatted table so you can see the data at a glance.

Before you run this cell, how many rows do you think a real retail dataset spanning multiple years would contain?

  • Run the cell by pressing Shift+Enter.

You should see Dataset shape: (9994, 21) printed, confirming all 9,994 rows and 21 columns loaded successfully.

Below that, the table preview shows columns including Sales, Profit, Discount, Category, Sub-Category, and Region.

Seeing an encoding or file-not-found error?

  • If you see a FileNotFoundError, make sure the CSV file is in the same folder as your notebook (superstore-analysis on your Desktop). The file name must match exactly: Sample-Superstore.csv.
  • If you see a UnicodeDecodeError, double-check that encoding="latin-1" is included in the read_csv call.

Help me fix my CSV loading error in Jupyter

✔️ Awesome, I've got everything!

Great. Save your notebook by pressing Ctrl+S (Cmd+S on macOS).

ⓧ I'd like to double check the full code

Here is the complete code for Cell 1:

# =============================================================
# Cell 1: Import Libraries and Load Data
# =============================================================
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Load the CSV file from your project folder
df = pd.read_csv("Sample-Superstore.csv", encoding="latin-1", parse_dates=["Order Date", "Ship Date"])
print(f"Dataset shape: {df.shape}")
print(f"\nFirst 5 rows:")
df.head()

Your environment is ready and the data is loaded. Next up, you will explore these 9,994 rows to understand what the numbers look like before transforming anything.

Explore and Get Your First Business Numbers

Your data is loaded and ready to talk. Now comes the exciting part: seeing what 9,994 real transactions actually look like in numbers.

Before cleaning or transforming anything, you want to see the raw data first. This gives you a baseline understanding of the numbers you are working with, and your first real output from the dataset.

In this step, get ready to:
  • Run summary statistics to see the range of Sales, Profit, and Discount values.
  • Check data quality by confirming there are no missing values or duplicate rows.
  • Compute total Sales and total Profit for the entire dataset to establish a baseline.
Run summary statistics on your data

The .describe() method gives you a statistical overview of your numeric columns in one shot. It shows the count, mean, standard deviation, min, max, and quartiles for each column you select.

Before you run this, take a guess: what do you think the average sale amount is across 9,994 retail transactions?

  • Create a new code cell below your existing cell by clicking the + button in the toolbar, then paste the following code:
# =============================================================
# Cell 3: Explore the Data
# =============================================================
print("Summary statistics for numeric columns:")
df[["Sales", "Quantity", "Discount", "Profit"]].describe()
  • Run the cell by pressing Shift+Enter.

What does this code do?

  • df[["Sales", "Quantity", "Discount", "Profit"]] selects only the four numeric columns you care about for this analysis.
  • .describe() computes count, mean, std, min, 25%, 50%, 75%, and max for each column. This is your fastest route to understanding the shape of your data.

You should see a table of statistics. Notice that Sales ranges from $0.44 to over $22,000, and Profit ranges from -$6,600 to +$8,400. Negative profit means some transactions are losing money.

Seeing a NameError?

  • Make sure you ran Cell 1 first. Jupyter cells must be executed in order for variables like df to exist.
  • If your kernel restarted, you need to re-run all previous cells. Go to Cell > Run All Above in the menu bar.

Help me debug my .describe() output

Check data quality and compute totals

Before you trust any numbers, you need to verify the data is clean. Missing values or duplicates would skew every calculation that follows.

  • Create another new code cell below by clicking +, then paste this code:
# =============================================================
# Cell 4: Check Data Quality
# =============================================================
print(f"Missing values per column:\n{df.isnull().sum()}")
print(f"\nDuplicate rows: {df.duplicated().sum()}")
print(f"\nCategories: {df['Category'].unique()}")
print(f"Regions: {df['Region'].unique()}")
print(f"\nTotal Sales: ${df['Sales'].sum():,.2f}")
print(f"Total Profit: ${df['Profit'].sum():,.2f}")
  • Run the cell by pressing Shift+Enter.

What does this code do?

  • .isnull().sum() counts missing values in every column. Zero across the board means your data is complete.
  • .duplicated().sum() counts exact duplicate rows. Zero means every row is unique.
  • .unique() on Category and Region reveals the dimensions of your data: 3 product categories and 4 US regions.
  • The final two lines compute totals. These headline numbers look healthy overall, but the story changes dramatically when you dig deeper in the next step.

You should see 0 missing values, 0 duplicate rows, 3 categories (Furniture, Office Supplies, Technology), and 4 regions (South, West, Central, East). The total Sales and Profit print at the bottom.

Seeing unexpected missing values?

  • If you see missing values in a column, that is likely fine for this dataset. The Postal Code column may show nulls for some entries, which does not affect Sales or Profit analysis.
  • If you see a KeyError, double-check the column names are exactly as shown: Category, Region, Sales. Column names are case-sensitive.

Help me understand my data quality results

✔️ Awesome, I've got everything!

Great. Make sure you have saved your notebook by pressing Ctrl+S (Cmd+S on macOS).

ⓧ I'd like to double check the full code

Your notebook should now have 3 cells in this order. Scroll through your notebook and confirm each is present:

  • Cell 1: Import Libraries and Load Data (the pd.read_csv() cell from Step 1).
  • Cell 2: Explore the Data (the .describe() cell from this step).
  • Cell 3: Check Data Quality (the .isnull().sum() and totals cell from this step).

You now have a baseline understanding of your data. The overall numbers look healthy, but did you notice that Profit can go deeply negative? Next up, you will engineer new features that let you dig into exactly where that money is disappearing.

Engineer Features That Enable Deeper Analysis

You have explored the raw data and confirmed the totals look healthy. Total sales and total profit are both positive. But to answer where money is being lost, you need columns that do not exist yet.

The raw dataset has full dates but no year or month breakdown for trend analysis. It has dollar amounts but no percentage-based profitability metric for comparing products at different price points. In this step, you will engineer four new columns that unlock the deeper questions.

In this step, get ready to:
  • Extract year and month from Order Date for time-based grouping.
  • Calculate profit margin percentages for cross-product comparison.
  • Compute shipping duration to understand delivery performance.
Create your feature engineering cell

Feature engineering means creating new columns from existing data to enable analysis that the raw columns alone cannot support.

What is feature engineering?

Think of it as upgrading your toolbox. The raw data gives you a full date like 2017-11-08, but you cannot easily group by year or month without extracting those pieces into their own columns.

Similarly, a product that makes $50 profit on a $100 sale is far more efficient than one making $50 on a $5,000 sale. A profit margin percentage column makes that comparison instant.

  • Create a new code cell in your Colab notebook by clicking the + Code button.
  • Paste the following code to add four new columns and verify them:
# =============================================================
# Cell 5: Feature Engineering
# =============================================================
df["Order_Year"] = df["Order Date"].dt.year
df["Order_Month"] = df["Order Date"].dt.month
df["Profit_Margin"] = (df["Profit"] / df["Sales"]) * 100
df["Shipping_Days"] = (df["Ship Date"] - df["Order Date"]).dt.days

print("New columns added: Order_Year, Order_Month, Profit_Margin, Shipping_Days")
print("\n5 lowest profit margin rows:")
print(df.nsmallest(5, "Profit_Margin")[["Product Name", "Sales", "Profit", "Profit_Margin", "Discount"]])
print(f"\nAverage shipping days by Ship Mode:")
print(df.groupby("Ship Mode")["Shipping_Days"].mean().sort_values())

What does this code do?

  • .dt.year and .dt.month use the pandas dt accessor to pull integer year and month values from your datetime column. This lets you group sales by year or month later.
  • Profit_Margin is calculated as (Profit / Sales) * 100, giving a percentage. A product with -50% margin is losing half its revenue on every sale.
  • Shipping_Days subtracts Order Date from Ship Date and extracts the integer number of days using .dt.days. This reveals how quickly each shipping mode delivers.
  • nsmallest(5, "Profit_Margin") shows the 5 worst-performing rows. These are products where heavy discounts destroyed all profit.

Before you run this cell, predict: will all the profit margin values be positive, or will some be negative?

  • Run the cell by pressing Shift+Enter.

You will see the 5 lowest margin rows showing deeply negative percentages (some below -200%). You will also see average shipping days ranging from about 0 days (Same Day) to about 5 days (Standard Class).

Seeing a TypeError or KeyError?

  • If you see a TypeError on the .dt line, your Order Date column may not be datetime type. Go back to Cell 2 and confirm your read_csv includes parse_dates=["Order Date", "Ship Date"]. Re-run Cell 2 before trying Cell 5 again.
  • If you see a KeyError, check for typos in column names. The dataset uses Order Date (with a space), not OrderDate or order_date.
  • help me debug a TypeError or KeyError in my feature engineering cell
Verify the new columns exist

The cell output already showed the 5 worst margins and shipping averages. Now confirm the Profit_Margin column appears alongside the original columns.

  • Run this quick check in a new cell:
df[["Sales", "Profit", "Profit_Margin"]].head(10)

You should see 10 rows with Sales, Profit, and your new Profit_Margin column showing percentage values. Some rows show negative margins, confirming that certain transactions lost money.

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook. Colab auto-saves, but you can press Ctrl+S to be sure.

ⓧ I'd like to double check the full code

Your notebook should now have 5 cells (plus the quick check). Scroll up and confirm each one is present in this order:

  • Cell 1: Upload the Dataset (files.upload() and filename extraction).
  • Cell 2: Import Libraries and Load Data (read_csv with encoding and date parsing, shape print, .head()).
  • Cell 3: Explore the Data (.describe() on numeric columns).
  • Cell 4: Check Data Quality (.isnull().sum(), duplicates, unique categories, totals).
  • Cell 5: Feature Engineering (4 new columns, lowest margins, shipping averages).

Your DataFrame now has 25 columns with four new features that raw data alone could not provide. Next up, you will use these features to uncover which product sub-categories are quietly losing money despite generating high revenue.

Uncover Where Money Is Made and Lost

Your DataFrame is clean and loaded with engineered features. Time to answer the business question: where is money being made and lost?

The trap most analysts fall into is looking at totals. Total sales across the company look healthy. But totals can hide entire product lines that are bleeding money. In this step, you will drill from the surface-level view down into sub-categories to find a non-obvious insight.

In this step, get ready to:
  • Run a category-level analysis that makes everything look fine.
  • Drill into sub-categories to reveal products that lose money despite generating revenue.
  • Investigate discounting as the cause of those losses.
Start with the naive view at category level

The most common first move in data analysis is to group by broad categories and sum the numbers. This gives you a high-level picture. It also gives you a false sense of security.

  • Create a new code cell in your notebook and add this code to group Sales and Profit by Category:
# =============================================================
# Cell 6: Naive Analysis - Sales by Category
# =============================================================
print("=== Total Sales by Category ===")
sales_by_category = df.groupby("Category")["Sales"].sum().sort_values(ascending=False)
print(sales_by_category)
print("\n=== Total Profit by Category ===")
profit_by_category = df.groupby("Category")["Profit"].sum().sort_values(ascending=False)
print(profit_by_category)
print("\nFurniture's profit margin looks suspiciously thin...")

What does this code do?

The .groupby("Category")["Sales"].sum() pattern is a grouped aggregation. It splits the 9,994 rows into groups (one per category), then sums the Sales column within each group.

Sorting with .sort_values(ascending=False) puts the highest-revenue category on top so you can spot the leader immediately.

  • Run the cell.

You should see Technology leading in Sales, followed by Furniture and Office Supplies. Profit also shows all three categories in positive territory. Everything looks healthy at this level.

But notice the last printed line: Furniture's profit margin looks thin relative to its sales. That is your clue to dig deeper.

Seeing a KeyError on Category?

  • Make sure the column name is exactly Category with a capital C. Column names are case-sensitive.
  • Confirm that your earlier cells (Cells 1-5) ran successfully. If the runtime disconnected, re-run all cells from the top.

Help me debug my groupby code

Go deeper into sub-categories

Category-level totals can mask what is happening underneath. A category might be profitable overall while individual sub-categories within it are losing thousands of dollars.

Before you run the next cell, ask yourself: based on the category totals you just saw, do you think every sub-category is profitable? Or might some surprise you?

  • Create a new code cell and add this code to break profit down by Sub-Category:
# =============================================================
# Cell 7: The Reveal - Profit by Sub-Category
# =============================================================
profit_by_subcat = df.groupby("Sub-Category")["Profit"].sum().sort_values()
print("=== Profit by Sub-Category (sorted worst to best) ===")
print(profit_by_subcat)
print("\nTables, Bookcases, and Supplies are LOSING money!")

What does this code do?

This groups by Sub-Category instead of Category, giving you 17 rows instead of 3. Sorting ascending (the default) puts the worst performers at the top of the output.

  • Run the cell.

Look at the bottom of the list. Tables shows roughly -$17K in profit. Bookcases shows around -$3K. Supplies shows about -$1K. These sub-categories generate revenue but lose money on every sale.

This is the core insight: high revenue does not mean high profit. The category-level view hid these losses because profitable sub-categories (like Copiers and Phones) compensated.

Not seeing negative values?

  • Make sure you are summing the Profit column, not Sales. Sales is always positive. Profit can be negative.
  • Confirm the column name is Sub-Category with a hyphen, not an underscore.

Help me understand my sub-category profit output

Investigate the cause behind the losses

You know which sub-categories are losing money. Now you need to answer why. The Discount column is the prime suspect. If loss-making products are being heavily discounted, that would explain how revenue turns into losses.

  • Create a new code cell and add this code to compare discount rates and regional margins:
# =============================================================
# Cell 8: Investigate - Discount Impact
# =============================================================
loss_makers = df[df["Sub-Category"].isin(["Tables", "Bookcases", "Supplies"])]
profitable = df[~df["Sub-Category"].isin(["Tables", "Bookcases", "Supplies"])]

print(f"Avg discount on LOSS-MAKING sub-categories: {loss_makers['Discount'].mean():.2%}")
print(f"Avg discount on PROFITABLE sub-categories: {profitable['Discount'].mean():.2%}")
print(f"\nLoss-makers get ~{loss_makers['Discount'].mean() / profitable['Discount'].mean():.1f}x higher discounts!")

print("\n=== Profit Margin by Region ===")
region_stats = df.groupby("Region")[["Sales", "Profit"]].sum()
region_stats["Profit_Margin_%"] = (region_stats["Profit"] / region_stats["Sales"]) * 100
print(region_stats.sort_values("Profit_Margin_%", ascending=False))

What does this code do?

  • The .isin() filter splits the full DataFrame into two groups: loss_makers (rows from the three money-losing sub-categories) and profitable (everything else).
  • Comparing .mean() of the Discount column between these two groups reveals whether discounting correlates with the losses.
  • The regional breakdown at the end adds geographic context, showing which regions turn revenue into profit most efficiently.
  • Run the cell.

You should see that the loss-making sub-categories have roughly 2-3x higher average discounts than the profitable ones. The regional table shows the West region leading in profit margin while the Central region has the thinnest margins.

Why does this matter?

You have just proven a causal link. Tables, Bookcases, and Supplies are not losing money because they sell poorly. They sell well. They lose money because excessive discounting eats all the profit and then some. This is exactly the kind of non-obvious insight that makes data analysis valuable.

🙋‍♀️ Seeing a ZeroDivisionError?

  • This can happen if the profitable DataFrame ended up empty. Check that the tilde ~ is present before df["Sub-Category"].isin(...) on the second line. The tilde inverts the filter to get everything NOT in the list.

Help me fix my discount comparison code

✔️ Awesome, I've got everything!

Great. Confirm you have saved your notebook. You should have Cells 6, 7, and 8 all producing output.

ⓧ I'd like to double check the full code

Scroll through your notebook and confirm these three cells exist in order, each producing output when run:

  • Cell 6: Naive Analysis - Sales by Category prints total Sales and Profit grouped by the 3 categories.
  • Cell 7: The Reveal - Profit by Sub-Category prints all 17 sub-categories sorted by profit, worst first.
  • Cell 8: Investigate - Discount Impact prints average discounts for loss-makers vs profitable items, plus a regional profit margin table.

You have uncovered the core business insight: Tables, Bookcases, and Supplies are bleeding money because they are over-discounted. Numbers in a table prove this to you, but they will not communicate the story to stakeholders. Next up, you will turn these findings into publication-quality visualizations that make the insight impossible to miss.

Visualize the Business Story

You've uncovered the core insight: some sub-categories bleed money despite generating revenue, and excessive discounting is the cause. Numbers in a grouped table prove this analytically, but they don't communicate the story at a glance.

Visualizations make the profit-vs-loss pattern immediately obvious to stakeholders. In this step, you'll create three publication-quality charts that turn your data findings into a compelling business narrative.

In this step, get ready to:
  • Create a horizontal bar chart showing which sub-categories profit and which lose money.
  • Create a scatter plot proving that higher discounts destroy profit.
  • Build a regional comparison and write a key findings summary.
Create the profit by sub-category chart

A bar chart with red and green coloring makes profitable and loss-making sub-categories instantly distinguishable. This is the single most impactful chart in your analysis because it answers the core question visually: where is money being made and lost?

Before you run this cell, think about what you expect. You already have the profit_by_subcat Series sorted from lowest to highest. Which sub-categories do you expect to appear as red (negative) bars?

  • Create a new code cell and paste the following chart code:
# =============================================================
# Cell 9: Visualization - Profit by Sub-Category
# =============================================================
plt.figure(figsize=(12, 6))
colors = ["#e74c3c" if x < 0 else "#2ecc71" for x in profit_by_subcat.values]
profit_by_subcat.plot(kind="barh", color=colors)
plt.title("Profit by Sub-Category: Winners vs. Losers", fontsize=14)
plt.xlabel("Total Profit ($)")
plt.ylabel("")
plt.axvline(x=0, color="black", linestyle="--", linewidth=0.8)
plt.tight_layout()
plt.show()

What does this code do?

  • The list comprehension assigns #e74c3c (red) to negative values and #2ecc71 (green) to positive values. This creates an instant visual split between winners and losers.
  • The axvline(x=0) draws a vertical dashed line at the $0 mark, making the profit/loss boundary crystal clear.
  • Using kind="barh" makes the bars horizontal so sub-category names are easy to read on the y-axis.
  • Run the cell.
  • You should see a horizontal bar chart where Tables, Bookcases, and Supplies appear as red bars extending left of the $0 line. All other sub-categories are green, with Copiers and Phones as the longest green bars.

Chart not displaying?

  • If you see a NameError: name 'profit_by_subcat' is not defined, go back and run Cell 7 first. Colab cells must be run in order.
  • If the chart appears but all bars are the same color, check that the list comprehension references profit_by_subcat.values (not profit_by_subcat alone).

Help me debug my matplotlib bar chart

Create the discount vs profit scatter plot

The bar chart shows WHICH sub-categories lose money. Now you need to prove WHY. A scatter plot of Discount vs Profit reveals the relationship visually: as discounts increase, profit plummets.

  • Create a new code cell and paste the following scatter plot code:
# =============================================================
# Cell 10: Visualization - Discount vs Profit Scatter
# =============================================================
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x="Discount", y="Profit", hue="Category", alpha=0.5)
plt.title("The Discount Trap: Higher Discounts Destroy Profit", fontsize=14)
plt.xlabel("Discount Applied")
plt.ylabel("Profit ($)")
plt.axhline(y=0, color="red", linestyle="--", linewidth=0.8)
plt.tight_layout()
plt.show()

What does this code do?

  • The sns.scatterplot function from seaborn plots each of the 9,994 transactions as a dot, with Discount on the x-axis and Profit on the y-axis.
  • The hue="Category" parameter colors dots by product category, so you can see which categories cluster in the loss zone.
  • The axhline(y=0) draws a horizontal red dashed line at $0 profit, clearly marking the boundary between profit and loss.
  • Run the cell.
  • You should see a cloud of dots that fans downward as discounts increase. Most dots above the red $0 line have low discounts (left side). Most dots below the line have high discounts (right side).

Scatter plot looks empty or has no legend?

  • Make sure you have import seaborn as sns in your Cell 2. If you skipped it, add it in a new cell and run it before re-running Cell 10.
  • If the plot renders but with no color differentiation, confirm that hue="Category" is spelled exactly as shown (capital C).

Help me fix my seaborn scatter plot

Compare regions and summarize your findings

Your last visualization compares the four regions side by side. This shows which regions are most efficient at converting revenue into profit.

  • Create a new code cell and paste the following regional comparison code:
# =============================================================
# Cell 11: Visualization - Regional Performance
# =============================================================
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

region_stats["Sales"].plot(kind="bar", ax=axes[0], color="steelblue")
axes[0].set_title("Sales by Region", fontsize=12)
axes[0].set_ylabel("Total Sales ($)")
axes[0].tick_params(axis="x", rotation=45)

region_stats["Profit"].plot(kind="bar", ax=axes[1], color="seagreen")
axes[1].set_title("Profit by Region", fontsize=12)
axes[1].set_ylabel("Total Profit ($)")
axes[1].tick_params(axis="x", rotation=45)

plt.tight_layout()
plt.show()

What does this code do?

  • The plt.subplots(1, 2) creates a figure with 1 row and 2 columns of charts placed side by side.
  • The left chart shows total Sales by Region. The right chart shows total Profit by Region. Comparing them lets you spot regions that generate high revenue but thin profit.
  • The tick_params(axis="x", rotation=45) rotates region names so they don't overlap.
  • Run the cell.
  • You should see two bar charts side by side. The West region leads in both Sales and Profit. The Central region has relatively high sales but thinner profit compared to other regions.

Seeing a NameError for region_stats?

  • Go back and run Cell 8 where region_stats is created. Each Colab cell depends on variables from earlier cells being run first.
  • If labels are overlapping or cut off, confirm you included plt.tight_layout() before plt.show().

Help me debug my subplot charts

Now wrap up your analysis with a text cell that summarizes what the data told you. This is what stakeholders read first when reviewing a notebook.

  • Create a new code cell and paste the following key findings summary:
# =============================================================
# Cell 12: Key Findings Summary
# =============================================================
print("=" * 50)
print("KEY FINDINGS")
print("=" * 50)
print("""
1. Tables, Bookcases, and Supplies lose money despite generating revenue.
   The culprit: excessive discounting (2-3x higher than profitable items).

2. Technology category (especially Copiers and Phones) drives the most
   profit with the healthiest margins.

3. Regional performance varies: West region leads in both sales and profit,
   while Central region has the thinnest margins.

RECOMMENDATION: Reduce discounts on Furniture (especially Tables) or
discontinue loss-making product lines to improve overall profitability.
""")

Why include a written summary?

Charts catch attention, but written findings tell the reader exactly what to take away. In a real data analyst role, your notebook is read by people who may scroll straight to the conclusion. The summary and recommendation make your analysis actionable.

  • Run the cell.
  • You should see a formatted text block with 3 numbered findings and a recommendation, separated by a line of equals signs at the top.

✔️ Awesome, I've got everything!

Great. Scroll through your notebook from top to bottom and confirm all four visualization cells (Cells 9-12) are showing their outputs.

ⓧ I'd like to double check the full code

Scroll through your notebook and confirm these cells are present in order, each producing its output:

  • Cell 9: Horizontal bar chart of Profit by Sub-Category with red/green coloring.
  • Cell 10: Scatter plot of Discount vs Profit colored by Category.
  • Cell 11: Side-by-side bar charts comparing Sales and Profit by Region.
  • Cell 12: Key findings summary with 3 bullet points and a recommendation.

If any cell shows no output, click into it and press the play button (or Shift+Enter) to re-run it.

Before you scroll through to confirm everything, predict: which of your three charts do you think communicates the strongest single insight to a non-technical audience?

Scroll through your entire notebook now. You should see three distinct charts displayed inline: the red/green bar chart, the scatter plot cloud, and the regional side-by-side comparison. Your notebook is complete and portfolio-ready.

Secret mission

Build a Monthly Sales Trend Analysis

Your static groupby analysis shows which products and regions are profitable, but it cannot reveal when sales peak and dip throughout the year. In this secret mission, build a time-series trend chart that uncovers seasonal patterns hiring managers love to see in portfolios.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in Google Colab, so there are no ongoing costs. Your notebook is saved automatically to your Google Drive. Decide whether to keep your resources as-is, disconnect the runtime, or delete the notebook entirely.

Resources you used:

  • Google Colab notebook (Superstore_Sales_Analysis.ipynb) saved in Google Drive.
  • Uploaded CSV file (ephemeral, stored only in runtime memory).

Keep everything running

No action needed. Choose this if you plan to revisit your analysis or continue experimenting with the dataset.

  • Your notebook stays in Google Drive indefinitely at no cost.
  • The uploaded CSV file disappears when the Colab runtime disconnects. This is normal. Your code to re-upload it is already in Cell 1, so you can reload it any time.
  • To share your analysis, click the Share button in the top-right corner of Colab and send the link to anyone with a Google account.

Pause - I'll come back to this later

Disconnect the runtime to free up Colab's compute resources. Your notebook code remains saved in Google Drive.

  • In your Colab notebook, click Runtime in the top menu bar.
  • Click Disconnect and delete runtime.
  • Your notebook file stays in Google Drive. When you reopen it later, you will need to re-upload the CSV and re-run all cells from the top.

Delete - I don't want to use this again

Remove the notebook from your Google Drive entirely.

  • Open Google Drive (drive.google.com).
  • Navigate to the Colab Notebooks folder.
  • Right-click Superstore_Sales_Analysis.ipynb and select Move to trash.
  • The uploaded CSV file is already gone once the runtime disconnected. No additional cleanup needed.

Nice Work!

Nice Work!

You just completed a full data analysis workflow, from raw CSV to actionable business insights backed by publication-quality charts. This is exactly the process hiring managers look for in entry-level data analyst portfolios.

You've learned how to:

  • Load and explore a real 9,994-row retail dataset in Google Colab, engineering features like Profit_Margin and time-based columns that unlock deeper analysis.
  • Discover a non-obvious business insight: Tables, Bookcases, and Supplies lose money despite generating revenue, caused by excessive discounting at 2-3x the rate of profitable items.
  • Create 3 publication-quality visualizations (bar chart, scatter plot, regional comparison) that communicate business insights at a glance and prove your findings to stakeholders.
  • Secret Mission: Built a monthly time-series trend revealing seasonal sales spikes in November-December each year, using pandas period grouping and matplotlib area shading.

Ready to quiz yourself?