Analyze Amazon Books with Pandas

Load, clean, and visualize Amazon bestselling books data using pandas.

Introduction

30 Second Summary

Every year, Amazon's bestseller list reveals what millions of readers are buying. Buried in that data are patterns about pricing, ratings, and which authors keep coming back year after year.

In this project, you will build a Jupyter Notebook that analyzes a real dataset of Amazon's top 50 bestselling books from 2009 to 2019. You'll use pandas to load, clean, filter, and visualize the data to uncover trends hidden in the numbers.

What You'll Build

Open your finished notebook and run it top-to-bottom to see 550 real book entries transform into clean tables, filtered insights, and colorful charts you can show to anyone.

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

  • A pandas DataFrame loaded from real Amazon data that you can explore, filter, and sort to answer any question about bestseller patterns.
  • A data cleaning workflow that catches 199 hidden duplicates and produces accurate author rankings you can trust.
  • Three matplotlib visualizations (a histogram and bar charts) revealing price distributions and genre comparisons at a glance.
  • Secret Mission: A year-over-year trend analysis showing how genre mix, average price, and ratings shifted from 2009 to 2019.

Are there any prerequisites?

You'll need basic Python knowledge (variables, loops, and functions). All tools for this project (Python, Jupyter Notebook, pandas, and matplotlib) are already installed on your machine.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

You have a real dataset of 550 Amazon bestselling books waiting to be explored. Take a moment to commit to the questions you want to answer with it.

Set Up Your Project

Before you can analyze Amazon's bestselling books, you need a workspace and the actual data. This step gets your project folder ready, grabs the dataset, and confirms your tools work.

You will download a real dataset from Kaggle, launch Jupyter Notebook, and verify that pandas and matplotlib import without errors.

In this step, get ready to:
  • Create a project folder and download the Amazon bestsellers dataset.
  • Launch Jupyter Notebook and create a new analysis notebook.
  • Confirm pandas and matplotlib import successfully.
Create your project folder and download the dataset

You need a dedicated folder on your Desktop to keep the dataset and notebook together. You also need a free Kaggle account to download the CSV file.

  • 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 and create your project folder by running these commands:
cd ~/Desktop
mkdir amazon-bestsellers
  • Confirm the folder was created by running:
ls

You should see amazon-bestsellers in the output.

  • Move into your new folder by running:
cd amazon-bestsellers

Now you need the dataset file. The file lives on Kaggle, a free platform for sharing datasets.

  • Open your browser and go to the dataset page:
  • https://www.kaggle.com/datasets/sootersaalu/amazon-top-50-bestselling-books-2009-2019
  • Sign in with a free Kaggle account. If you do not have one, click Register and follow the sign-up prompts.
  • Click the Download button near the top of the dataset page. This downloads a zip file to your Downloads folder.
  • Unzip the downloaded file. Inside you will find bestsellers with categories.csv.
  • Move bestsellers with categories.csv into your amazon-bestsellers folder on the Desktop.
  • Back in your terminal, confirm the file is in your folder by running:
ls

You should see bestsellers with categories.csv in the output.

Don't see the CSV file?

Make sure you unzipped the downloaded file. The download from Kaggle arrives as a .zip archive, not a raw CSV.

Double-check you moved the file into the amazon-bestsellers folder on your Desktop, not into a subfolder or your Downloads.

help me find my downloaded Kaggle CSV file

Launch Jupyter Notebook and create a new notebook

Jupyter Notebook gives you an interactive environment where you can write Python code in cells and see results immediately.

  • Start the Jupyter Notebook server by running this command in your terminal:
jupyter notebook

What does this command do?

This starts a local web server and automatically opens the Jupyter dashboard in your browser. The command takes over your terminal while the server runs.

To stop the server later, press Ctrl+C in the terminal. For now, leave it running.

Your browser opens to the Jupyter dashboard, showing the files in your amazon-bestsellers folder. You should see bestsellers with categories.csv listed.

  • Click the New button in the top-right area of the dashboard.
  • Select Python 3 from the dropdown menu.

A new notebook opens in a separate browser tab with an empty code cell ready for input.

  • Click the notebook name at the top of the page (it says Untitled).
  • In the rename dialog, type amazon_bestsellers_analysis and click Rename.

Your notebook is now saved as amazon_bestsellers_analysis.ipynb in your project folder.

Browser didn't open automatically?

Look at the terminal output. You should see a URL starting with http://localhost:8888. Copy and paste it into your browser manually.

help me access my Jupyter Notebook dashboard

Import pandas and matplotlib

Every analysis notebook starts with importing the libraries you need. This confirms your Python environment is correctly set up.

  • Click inside the first empty cell in your notebook and type the following code:
# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt

What does this code do?

The first line imports pandas and gives it the alias pd. You will use pd throughout the notebook to load data, filter rows, and compute statistics.

The second line imports matplotlib's pyplot module as plt. This gives you charting functions for histograms, bar charts, and line plots.

Before you run this cell, what do you expect to see if both libraries are installed correctly?

  • Run the cell by pressing Shift+Enter.

You should see no output and no error messages. The cell number changes from In [ ]: to In [1]:. That confirms both libraries loaded successfully.

Seeing an ImportError?

If you see ModuleNotFoundError: No module named 'pandas', pandas is not installed in your current Python environment. Open a new terminal and run:

pip install pandas matplotlib

Then restart the notebook kernel by clicking Kernel in the menu bar, then Restart. Re-run the cell.

help me fix my pandas import error in Jupyter

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook by pressing Ctrl+S (Windows) or Cmd+S (macOS).

ⓧ I'd like to double check the full code

Your notebook should have one cell containing exactly this code:

# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt

Your workspace is ready with the dataset in place and your tools confirmed. Next up, you will load the CSV into a pandas DataFrame and get your first look at real data.

Import Data and Explore Your DataFrame

Your notebook is running and your imports are ready. Now it's time to bring real data into the notebook and see it rendered as a table.

Before you can spot trends or answer questions about bestselling books, you need to load the CSV file into a DataFrame and understand its structure. This step gives you your first look at the data.

In this step, get ready to:
  • Load the CSV dataset into a pandas DataFrame and display the first rows.
  • Inspect the DataFrame's shape, column names, data types, and null counts.
  • Generate summary statistics for all numeric columns.
Load the dataset into a DataFrame
  • In your notebook, click into a new empty cell below Cell 1.
  • Load the CSV and display the first 5 rows by pasting this code into the cell:
# Cell 2: Load and inspect data
df = pd.read_csv('bestsellers with categories.csv')
df.head()
  • Run the cell by pressing Shift+Enter.

What does this code do?

  • pd.read_csv('bestsellers with categories.csv') reads the CSV file from disk and converts it into a pandas DataFrame stored in the variable df.
  • df.head() displays the first 5 rows as a formatted table directly in the notebook output. This is your first glimpse of the raw data.

You should see a table with 5 rows and columns: Name, Author, User Rating, Reviews, Price, Year, and Genre.

Seeing a FileNotFoundError?

  • Make sure bestsellers with categories.csv is in the same folder where you launched jupyter notebook. The filename includes spaces, not underscores.
  • Check the filename matches exactly, including the lowercase spelling and .csv extension.

Help me debug a FileNotFoundError when loading my CSV in pandas.

Explore the DataFrame structure
  • Click into the next empty cell.
  • Inspect the DataFrame's dimensions, columns, and types by pasting this code:
# Cell 3: DataFrame structure
print("Shape:", df.shape)
print("\nColumns:", df.columns.tolist())
print("\nData types:")
print(df.dtypes)
print("\nInfo:")
df.info()
  • Run the cell by pressing Shift+Enter.

What does this code do?

  • df.shape returns the number of rows and columns as a tuple.
  • df.columns.tolist() prints all column names as a plain Python list.
  • df.dtypes shows the data type of each column (object for text, float64/int64 for numbers).
  • df.info() gives a full summary including how many non-null values exist per column. Zero nulls means no missing data to worry about.

Before you look at the output, what do you think df.shape will return? How many rows and columns do you expect?

You should see Shape: (550, 7) confirming 550 entries across 7 columns, with zero null values in the info() output.

Seeing a NameError for df?

This means Cell 2 did not run successfully. Return to Cell 2, fix any errors, and re-run it with Shift+Enter before running Cell 3.

Help me fix a NameError when I try to use my DataFrame.

Generate summary statistics
  • Click into the next empty cell.
  • Generate summary statistics for all numeric columns by pasting this code:
# Cell 4: Summary statistics
df.describe()
  • Run the cell by pressing Shift+Enter.

What does describe() show you?

df.describe() computes count, mean, standard deviation, min, max, and quartiles for every numeric column. It gives you a quick health check of the data.

Look for the mean price (~$13.10), the rating range (3.3 to 4.9), and the massive range in Reviews. These numbers tell you the data is real and varied.

You should see a formatted table with summary statistics for User Rating, Reviews, Price, and Year.

Output looks empty or incomplete?

Make sure df.describe() is the last line in the cell. In Jupyter, only the last expression in a cell is displayed automatically.

Help me understand why my Jupyter cell isn't showing output.

✔️ Awesome, I've got everything!

Great. Make sure you save your notebook with Ctrl+S (Windows) or Cmd+S (macOS).

ⓧ I'd like to double check the full code

Here is everything your notebook should contain so far across all 4 cells:

# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt

# Cell 2: Load and inspect data
df = pd.read_csv('bestsellers with categories.csv')
df.head()

# Cell 3: DataFrame structure
print("Shape:", df.shape)
print("\nColumns:", df.columns.tolist())
print("\nData types:")
print(df.dtypes)
print("\nInfo:")
df.info()

# Cell 4: Summary statistics
df.describe()

Your DataFrame is loaded and you have a clear picture of its structure. Next up, you'll dig deeper into specific statistics and uncover a hidden data quality problem.

Summary Statistics and the Duplicate Problem

Your DataFrame is loaded, healthy, and ready for questions. Now it's time to dig into what the data actually tells you about bestselling books.

You want to know which authors dominate the bestseller list and how genres compare. This dataset counts the same book every year it appeared as a bestseller. Your first author ranking will look suspiciously inflated, leading you to uncover a data quality problem hiding in plain sight.

In this step, get ready to:
  • Compute genre distribution and top author appearance counts.
  • Calculate individual summary statistics and unique value counts.
  • Investigate suspicious author counts and confirm duplicate rows exist.
Count genres and top authors

Before diving into individual numbers, you want a high-level view of the dataset's composition. How many books are Fiction vs Non Fiction? Which authors show up the most?

  • Create a new cell in your notebook (Cell 5) and add the following code to count genres and top authors:
# Cell 5: Genre and author counts
print("Genre distribution:")
print(df['Genre'].value_counts())
print("\nTop 10 authors by appearances:")
print(df['Author'].value_counts().head(10))

What does this code do?

The .value_counts() method counts how many times each unique value appears in a column. It returns results sorted from most frequent to least frequent.

Adding .head(10) limits the output to the top 10 results so you can focus on the most prolific authors.

  • Run the cell by pressing Shift+Enter.
  • You should see Non Fiction at 310 rows and Fiction at 240 rows. In the author list, Jeff Kinney appears at the top with 12 appearances.

12 appearances for one author seems like a lot. Keep that number in mind.

Not seeing any output?

Make sure you ran Cell 2 first (the cell that loads the CSV into df). If you restarted the notebook kernel, you need to re-run all previous cells in order.

Check that the column name uses the exact capitalization from the dataset: 'Genre' and 'Author' both start with a capital letter.

Help me debug my value_counts() output

Compute individual statistics

You have the big picture from df.describe() in the previous step. Now you can pull out specific numbers and check how many unique values each column holds.

  • Create a new cell (Cell 6) and add this code to compute targeted statistics:
# Cell 6: Individual statistics
print(f"Mean rating: {df['User Rating'].mean():.2f}")
print(f"Median price: ${df['Price'].median():.2f}")
print(f"Total reviews: {df['Reviews'].sum():,}")
print(f"\nUnique values per column:")
print(df.nunique())

What does this code do?

  • .mean() computes the average. .median() gives the middle value (less affected by extreme outliers than the mean).
  • .sum() adds up all values in the column. The :, format specifier adds comma separators to large numbers.
  • .nunique() counts how many distinct values each column has. This is your clue detector. If Name has fewer unique values than total rows, some books must appear more than once.
  • Run the cell.
  • You should see the mean rating is 4.62, the median price is $11.00, and the Name column shows 351 unique values out of 550 rows.

351 unique book titles but 550 rows. That gap of nearly 200 rows means many books are repeated in the dataset.

Seeing a KeyError?

Column names must match exactly, including spaces. The rating column is 'User Rating' (with a space), not 'UserRating' or 'user rating'. Run df.columns.tolist() to see the exact column names.

Help me fix my KeyError

Investigate the duplicate problem

Jeff Kinney has 12 appearances. The Name column has only 351 unique values across 550 rows. Something is off. Time to investigate.

  • Create a new cell (Cell 7) and add this code to investigate Jeff Kinney's entries and count duplicates:
# Cell 7: Investigate duplicates
print("Jeff Kinney's books:")
print(df[df['Author'] == 'Jeff Kinney'][['Name', 'Year', 'Price']])
print(f"\nDuplicate rows (same Name + Author): {df.duplicated(subset=['Name', 'Author']).sum()}")

What does this code do?

  • df[df['Author'] == 'Jeff Kinney'] filters the DataFrame to show only rows where the Author is Jeff Kinney. Adding [['Name', 'Year', 'Price']] selects just those three columns so you can see the pattern clearly.
  • .duplicated(subset=['Name', 'Author']) marks a row as True if another row has the same Name and Author combination. .sum() counts how many duplicates exist across the entire dataset.

Before you run this cell, think about it. How many duplicate rows do you expect to find, given that there are 550 total rows and 351 unique book titles?

  • Run the cell.

You should see Jeff Kinney's books like "Diary of a Wimpy Kid" appearing across multiple years with the same price. The final line confirms 199 duplicate rows exist.

Why do these duplicates exist?

This dataset records the top 50 bestselling books per year from 2009 to 2019. If a book was a bestseller in 2015 AND 2016, it gets a row for each year. That is useful for tracking yearly trends, but it inflates author counts and skews averages when you want to analyze unique books.

550 total rows minus 351 unique titles equals 199 duplicate entries. Your author rankings from Cell 5 are counting yearly appearances, not unique books. You will fix this in the next step.

Duplicate count showing 0?

Make sure the subset parameter uses a list with both column names: subset=['Name', 'Author']. If you pass only one column (e.g. subset='Name' without brackets), pandas checks just that one column and may give different results.

Help me understand why my duplicate count is wrong

✔️ Awesome, I've got everything!

Great. Make sure your notebook is saved (Ctrl+S or Cmd+S).

ⓧ I'd like to double check the full code

Scroll through your notebook and confirm these cells are present in order:

  • Cell 1: Imports (pandas and matplotlib).
  • Cell 2: Load and inspect data (pd.read_csv() and df.head()).
  • Cell 3: DataFrame structure (shape, columns, dtypes, info()).
  • Cell 4: Summary statistics (df.describe()).
  • Cell 5: Genre and author counts (value_counts() on Genre and Author).
  • Cell 6: Individual statistics (mean, median, sum, nunique).
  • Cell 7: Investigate duplicates (Jeff Kinney filter and duplicated() count).

You have uncovered the data quality problem. Jeff Kinney's 12 appearances do not mean 12 unique books. They mean a handful of books repeated across years. Next up, you will clean the data so your analysis reflects unique bestsellers instead of inflated counts.

Clean the Data

In the last step, you discovered that 199 rows in the dataset are duplicates. The same book appears once for every year it made the bestseller list, inflating author counts and skewing averages.

Cleaning these duplicates gives you an accurate picture of unique bestsellers. In this step, you will remove the repeated rows, rename columns for easier coding, and verify that your author rankings now reflect reality.

In this step, get ready to:
  • Remove duplicate rows to create a clean DataFrame of 351 unique books.
  • Rename columns to lowercase for cleaner syntax.
  • Re-run author analysis to verify corrected counts.
Drop duplicate rows

The pandas method drop_duplicates() removes rows where specified columns have repeated values. You will keep only the first occurrence of each book.

  • Create a new cell below Cell 7 and paste the following code:
# Cell 8: Clean duplicates
df_clean = df.drop_duplicates(subset=['Name', 'Author'])
print(f"Original shape: {df.shape}")
print(f"Cleaned shape: {df_clean.shape}")

What does this code do?

drop_duplicates(subset=['Name', 'Author']) looks at just the Name and Author columns. If two rows have the same book title by the same author, it keeps only the first and discards the rest.

The result is stored in a new DataFrame called df_clean, leaving your original df untouched for later use.

  • Run the cell by pressing Shift+Enter.
  • You should see output showing the original shape as (550, 7) and the cleaned shape as (351, 7).

Still seeing 550 rows in df_clean?

Make sure you spelled the subset column names exactly as they appear in the dataset: 'Name' and 'Author' with capital letters. Column names are case-sensitive.

Help me figure out why drop_duplicates is not reducing my row count

Rename columns for cleaner syntax

Column names like User Rating require bracket notation with quotes every time you reference them. Renaming to lowercase single-word names means you can type less and read faster.

  • Create a new cell below Cell 8 and paste the following code:
# Cell 9: Rename columns
df_clean = df_clean.rename(columns={
    'User Rating': 'rating',
    'Reviews': 'reviews',
    'Price': 'price',
    'Year': 'year',
    'Genre': 'genre',
    'Name': 'title',
    'Author': 'author'
})
print(df_clean.columns.tolist())

What does this code do?

The .rename(columns={...}) method takes a dictionary mapping old names to new names. It returns a new DataFrame with the updated column headers.

By reassigning the result back to df_clean, you overwrite the old version with the renamed version. From this point forward, all your column references will use the lowercase names.

  • Run the cell by pressing Shift+Enter.
  • You should see the output: ['rating', 'reviews', 'price', 'year', 'genre', 'title', 'author'].

Seeing a KeyError?

If you get a KeyError, you may have already renamed the columns in a previous run of this cell. Restart from Cell 8 (re-run it to recreate df_clean with the original column names), then run Cell 9 again.

Help me fix a KeyError when renaming columns in pandas

Re-run author analysis on cleaned data

In Step 3, Jeff Kinney topped the author list with 12 appearances. Those were inflated by the same books appearing in multiple years. Now you can see the true count of unique bestselling books per author.

  • Create a new cell below Cell 9 and paste the following code:
# Cell 10: Corrected author counts
print("Top 10 authors (unique books):")
print(df_clean['author'].value_counts().head(10))

What does this code do?

This is the same value_counts() operation from Step 3, but now it runs on the deduplicated df_clean DataFrame with the renamed author column. Each count now represents a distinct book, not yearly appearances.

Before you run this cell, think about what you expect to see. Will Jeff Kinney still have 12 books, or will the count drop?

  • Run the cell by pressing Shift+Enter.

The counts are now lower across the board. Jeff Kinney's count dropped from 12 appearances to the number of unique books he actually wrote on the list. This is the accurate ranking you can trust for analysis.

Seeing a KeyError on 'author'?

If pandas cannot find the author column, Cell 9 did not run successfully. Go back and re-run Cell 9 first, then try Cell 10 again.

Help me debug why my renamed column is not found

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook (press Cmd+S on macOS or Ctrl+S on Windows).

ⓧ I'd like to double check the full code

Your notebook should now have Cells 1 through 10. Scroll through your notebook and confirm these cells are present in order:

  • Cell 1: Imports (import pandas as pd and import matplotlib.pyplot as plt).
  • Cell 2: Load and inspect data (pd.read_csv() and df.head()).
  • Cell 3: DataFrame structure (shape, columns, dtypes, info()).
  • Cell 4: Summary statistics (df.describe()).
  • Cell 5: Genre and author counts.
  • Cell 6: Individual statistics.
  • Cell 7: Investigate duplicates.
  • Cell 8: Clean duplicates (drop_duplicates() producing shape (351, 7)).
  • Cell 9: Rename columns (all lowercase).
  • Cell 10: Corrected author counts (df_clean['author'].value_counts().head(10)).

Your data is now clean and well-structured. Next up, you will use boolean indexing and sorting to slice this DataFrame and answer targeted questions like "what are the highest-rated fiction books under $10?"

Filter and Sort the Data

In the last step, you cleaned your data and produced an accurate picture of 351 unique bestselling books. Now you can start asking targeted questions of that dataset.

Pandas lets you slice a DataFrame using boolean conditions, then sort the results by any column. In this step, you will combine filtering and sorting to answer questions like "which highly-rated Fiction books cost under $10?" and "which authors have the most unique bestsellers?"

In this step, get ready to:
  • Filter your DataFrame with boolean indexing and multiple conditions.
  • Sort and rank books by review count.
  • Aggregate data with groupby to compare genres and rank authors.
Filter with boolean indexing

Boolean indexing lets you pass a True/False condition inside square brackets to select only the rows that match. You can chain multiple conditions together with & (AND) to narrow your results further.

  • Create a new cell in your notebook and filter for Fiction books rated above 4.7 by running this code:
# Cell 11: Filtering - Fiction books rated above 4.7
high_rated_fiction = df_clean[(df_clean['genre'] == 'Fiction') & (df_clean['rating'] > 4.7)]
print(f"Fiction books rated above 4.7: {len(high_rated_fiction)}")
print(high_rated_fiction[['title', 'author', 'rating', 'price']].head())

What does this code do?

The expression inside the square brackets creates a boolean mask. Each condition wrapped in parentheses evaluates to True or False for every row.

  • df_clean['genre'] == 'Fiction' checks if the genre column equals "Fiction" for each row.
  • The & operator combines both conditions so only rows where BOTH are True survive the filter.
  • The result is stored in high_rated_fiction, a new smaller DataFrame containing only matching rows.
  • Run the cell. You should see a count of Fiction books rated above 4.7, followed by a table showing titles, authors, ratings, and prices.

Now add a price constraint to narrow the results even further. You want to find the best deals: highly-rated Fiction for under $10.

  • Create a new cell and add a third condition to your filter by running this code:
# Cell 12: Filtering with price constraint
cheap_great_fiction = df_clean[
    (df_clean['genre'] == 'Fiction') &
    (df_clean['rating'] > 4.7) &
    (df_clean['price'] < 10)
]
print(f"Highly-rated fiction under $10: {len(cheap_great_fiction)}")
print(cheap_great_fiction[['title', 'author', 'rating', 'price']])

What does this code do?

This extends the previous filter with a third condition: df_clean['price'] < 10. All three conditions must be True for a row to appear in the result.

Notice that this time the code prints ALL matching rows (no .head()) because the filtered set is small enough to display completely.

Before you run this cell, how many books do you think will match all three conditions?

  • Run the cell. You should see a filtered DataFrame showing specific titles, authors, ratings, and prices for highly-rated Fiction books under $10.

Seeing an empty DataFrame?

  • Make sure you are using df_clean (the cleaned DataFrame with lowercase column names), not df (the original with capitalized names).
  • Check that each condition is wrapped in its own parentheses. Missing parentheses around a condition causes operator precedence errors.
  • Verify the genre string matches exactly: 'Fiction' with a capital F.

Why is my boolean filter returning an empty result?

Sort and rank results

Filtering tells you which books match your criteria. Sorting tells you which ones stand out the most within that group. The .nlargest() method is a shortcut for sorting and slicing the top N rows in one step.

  • Create a new cell and find the 10 most-reviewed books across the entire cleaned dataset by running:
# Cell 13: Sorting
print("Top 10 most-reviewed books:")
print(df_clean.nlargest(10, 'reviews')[['title', 'author', 'reviews', 'rating']])

What does nlargest do?

The .nlargest(10, 'reviews') method returns the 10 rows with the highest values in the reviews column. It is equivalent to sorting the entire DataFrame by reviews in descending order and taking the first 10 rows, but faster for large datasets.

  • Run the cell. You should see a table of 10 books ranked from highest to lowest review count, with titles, authors, reviews, and ratings displayed.
Combine groupby with sorting

The .groupby() method splits your DataFrame into groups based on a column, then applies an aggregation (like mean or count) to each group. This lets you compare Fiction vs Non Fiction at a glance.

  • Create a new cell and compare average rating, price, and reviews between genres by running:
# Cell 14: Groupby genre comparison
print("\nGenre comparison (averages):")
print(df_clean.groupby('genre')[['rating', 'price', 'reviews']].mean())
  • Run the cell. You should see a table with two rows (Fiction, Non Fiction) and three columns showing the mean rating, price, and reviews for each genre.

What does groupby do here?

The .groupby('genre') splits the 351 rows into two groups (Fiction and Non Fiction). Then .mean() computes the average of each numeric column within each group. The result is a compact comparison table.

  • Create one more cell and rank authors by how many unique bestselling books they produced:
# Cell 15: Authors ranked by unique book count
print("\nAuthors by unique bestselling books:")
print(df_clean.groupby('author')['title'].count().sort_values(ascending=False).head(10))

What does this code do?

This chains three operations together. First, .groupby('author')['title'].count() counts titles per author. Then .sort_values(ascending=False) puts the highest counts first. Finally .head(10) keeps only the top 10.

Before you run this, which authors do you think will appear in the top 10?

  • Run the cell. You should see a ranked list of 10 authors with the count of unique bestselling books each one produced.

Seeing a KeyError?

  • Make sure you are referencing 'author' and 'title' (lowercase). These are the renamed column names from Step 4.
  • If you restarted your kernel, you need to re-run all previous cells first. Use Kernel > Restart & Run All or run each cell from Cell 1 down.

Why am I getting a KeyError in my groupby cell?

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook with all five new cells (Cells 11 through 15) producing output.

ⓧ I'd like to double check the full code

Scroll up through your notebook and confirm these cells are present in order. Each cell should be showing output beneath it:

  • Cell 1: Imports (pandas and matplotlib).
  • Cell 2: Load and inspect data.
  • Cell 3: DataFrame structure.
  • Cell 4: Summary statistics.
  • Cell 5: Genre and author counts.
  • Cell 6: Individual statistics.
  • Cell 7: Investigate duplicates.
  • Cell 8: Clean duplicates.
  • Cell 9: Rename columns.
  • Cell 10: Corrected author counts.
  • Cell 11: Filtering - Fiction books rated above 4.7.
  • Cell 12: Filtering with price constraint.
  • Cell 13: Sorting (top 10 most-reviewed).
  • Cell 14: Groupby genre comparison.
  • Cell 15: Authors ranked by unique book count.

Your notebook can now slice data by any combination of conditions and rank results by any column. Next up, you will turn these numbers into visualizations that make patterns jump off the screen.

Visualize Your Findings

Your filtering and sorting skills can slice the data any way you want. But staring at rows of numbers makes it hard to spot patterns at a glance.

Visualizations make those patterns obvious instantly. In this step, you'll use matplotlib (which you imported back in Cell 1) to create three charts that turn your pandas analysis into something visual and shareable.

In this step, get ready to:
  • Create a histogram showing how book prices are distributed.
  • Create a bar chart comparing Fiction vs Non Fiction averages.
  • Create a horizontal bar chart ranking the top 10 authors by unique books.
Create a price distribution histogram

A histogram groups continuous values into bins and shows how many items fall into each bin. This is perfect for seeing where most book prices cluster.

Before you run this, what do you think the price distribution looks like? Do you expect most books to be evenly spread across prices, or clustered in a particular range?

  • Create a new cell in your notebook (Cell 16) and add this code to plot a histogram of the price column:
# Cell 16: Price distribution histogram
df_clean['price'].plot(kind='hist', bins=15, title='Price Distribution of Bestselling Books',
                       xlabel='Price ($)', ylabel='Count')
plt.tight_layout()
plt.show()

What does this code do?

  • df_clean['price'].plot(kind='hist', bins=15, ...) tells pandas to draw a histogram with 15 bins from the price column.
  • plt.tight_layout() prevents labels from getting cut off at the edges of the figure.
  • plt.show() renders the chart inline in your notebook cell output.
  • Run the cell by pressing Shift+Enter.

You should see a histogram appear directly below the cell. Most books cluster in the $0-$20 range, with a long tail extending toward higher prices.

Don't see a chart?

  • Make sure you ran Cell 1 (the imports cell) earlier in this session. If you restarted the kernel, re-run all cells from the top.
  • Check that you typed the column name as lowercase 'price' (the renamed version from Cell 9), not 'Price'.
  • Why isn't my matplotlib chart rendering in the notebook?
Create a genre comparison bar chart

You ran a groupby comparison back in Cell 14 and saw the numbers. Now you'll turn those same averages into a side-by-side bar chart that makes the Fiction vs Non Fiction difference obvious at a glance.

  • Create Cell 17 and add this code to compute genre averages and plot them as a bar chart:
# Cell 17: Genre comparison bar chart
genre_stats = df_clean.groupby('genre')[['rating', 'price']].mean()
genre_stats.plot(kind='bar', title='Fiction vs Non Fiction: Average Rating and Price')
plt.tight_layout()
plt.show()

What does this code do?

  • genre_stats = df_clean.groupby('genre')[['rating', 'price']].mean() groups by genre and computes the average rating and price for each.
  • genre_stats.plot(kind='bar', ...) draws a grouped bar chart. Each genre gets a cluster of bars, one per numeric column.
  • Run the cell.

You should see a bar chart with two clusters (Fiction and Non Fiction), each showing bars for average rating and average price.

Bars look oddly scaled?

  • The rating bars (around 4.6) and price bars (around $11-$14) are plotted on the same y-axis. This is expected. The chart shows relative differences between genres, not absolute comparison between rating and price.
  • Why do my bar chart bars look oddly scaled when plotting rating and price together?
Create a top-10 authors horizontal bar chart

Your final visualization ranks the top 10 authors by how many unique bestselling books they produced. A horizontal bar chart makes long author names easy to read.

  • Create Cell 18 and add this code to count and plot the top 10 authors:
# Cell 18: Top 10 authors bar chart
top_authors = df_clean['author'].value_counts().head(10)
top_authors.plot(kind='barh', title='Top 10 Authors by Unique Bestselling Books',
                 xlabel='Number of Books')
plt.tight_layout()
plt.show()

What does this code do?

  • top_authors = df_clean['author'].value_counts().head(10) counts unique books per author and keeps the top 10. This uses the cleaned DataFrame, so counts reflect unique titles, not yearly repeats.
  • kind='barh' draws horizontal bars instead of vertical ones. This avoids cramming long author names into rotated x-axis labels.
  • Run the cell.

You should see a horizontal bar chart with author names on the y-axis and book counts on the x-axis. The authors are ranked from most to fewest unique bestselling books.

Author names are cut off?

  • Make sure plt.tight_layout() is called before plt.show(). This adjusts the figure margins so long labels fit.
  • How can I fix cut-off labels in my matplotlib chart?

✔️ Awesome, I've got everything!

Great. Make sure you've saved your notebook (Ctrl+S or Cmd+S).

ⓧ I'd like to double check the full code

Scroll through your notebook and confirm these three cells are present in order, after your filtering and sorting cells:

  • Cell 16 — Price distribution histogram with kind='hist' and bins=15.
  • Cell 17 — Genre comparison bar chart using genre_stats from a groupby on genre.
  • Cell 18 — Top 10 authors horizontal bar chart using top_authors with kind='barh'.

Each cell should end with plt.tight_layout() followed by plt.show(), and each should display a chart inline when run.

Scroll through your notebook from top to bottom. You now have 18 cells covering the entire exploratory data analysis workflow: importing, exploring, cleaning, filtering, sorting, aggregating, and visualizing.

Secret mission

Year-Over-Year Trend Analysis

You have all the pandas skills you need. Can you combine groupby, aggregation, and visualization to reveal how Amazon's bestseller list evolved over a decade? Build trend charts that show genre shifts, price changes, and rating patterns from 2009 to 2019.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project folder for reference, pause the running Jupyter server to free up your terminal, or delete everything entirely.

Resources you used:

  • Project folder on your Desktop containing amazon_bestsellers_analysis.ipynb and bestsellers with categories.csv.
  • A running Jupyter Notebook server process in your terminal.

Keep everything running

No action needed. Choose this if you want to keep exploring the dataset or add more analysis cells to your notebook.

  • Your notebook and CSV remain in the project folder on your Desktop.
  • Your Jupyter server stays running so you can return to the notebook in your browser.
  • To export a shareable copy, go to File > Download as > HTML (.html) in your notebook menu.

Pause - I'll come back to this later

Shut down the Jupyter server to free up your terminal. Your files stay on disk so you can restart later.

  • Switch to the terminal where Jupyter is running.
  • Press Ctrl+C to stop the Jupyter Notebook server. Confirm with y if prompted.
  • Your project folder, notebook, and CSV file remain on your Desktop for next time.
  • To resume later, open a terminal in your project folder and run jupyter notebook again.

Delete - I don't want to use this again

Remove all project files and stop the Jupyter server. This frees up disk space and cleans your Desktop.

  • Switch to the terminal where Jupyter is running and press Ctrl+C to stop the server. Confirm with y if prompted.
  • Delete the project folder by running the command for your operating system.

macOS

rm -rf ~/Desktop/amazon-bestsellers

Windows

Remove-Item -Recurse -Force ~\Desktop\amazon-bestsellers

This removes your notebook, the CSV dataset, and the project folder entirely.

Nice Work!

Nice Work!

You just completed a full exploratory data analysis of Amazon's bestselling books using pandas and matplotlib. From raw CSV to cleaned data to polished visualizations, your notebook tells a complete story about a decade of bestsellers.

You learned how to:

  • Loaded a real-world CSV dataset into a pandas DataFrame, identified a data quality issue (199 duplicate rows), and cleaned it to produce accurate author rankings.
  • Used boolean indexing with multiple conditions and groupby aggregation to answer targeted questions like "highest-rated fiction books under $10" and compare genre averages.
  • Created matplotlib visualizations (histograms, bar charts, horizontal bar charts) that reveal pricing distributions and author dominance at a glance.
  • Secret Mission: Built a year-over-year trend analysis with stacked bar charts and line charts showing how genre mix, average price, and ratings shifted from 2009 to 2019.

Ready to quiz yourself?