Analyze Sales Data: SQL to Pandas

Translate SQL queries into Pandas and analyze a dataset side by side.

Introduction

30 Second Summary

You already know how to pull insights from data with SQL. Switching to Python for data analysis can feel like starting from scratch, even when the logic is the same.

In this project, you will build a Jupyter Notebook that analyzes a restaurant tipping dataset using both SQL and Pandas side by side. You will run each SQL query next to its Pandas equivalent and see identical results.

What You'll Build

You will open your notebook, run a SQL query in one cell and the Pandas translation in the next, and see the same table of results printed under both.

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

  • A local data analysis environment with VS Code, Jupyter, and pandas, set up and ready for future data science projects.
  • Side-by-side SQL and Pandas cells in a notebook that produce identical results, proving you can translate any query you know into Pandas.
  • A proven fix for the groupby index trap, a common Pandas gotcha that does not exist in SQL.
  • Secret Mission: Join two DataFrames with merge() to replicate a SQL JOIN and discover how Pandas handles join types and null keys differently from SQL.

Are there any prerequisites?

This project assumes you are comfortable with SQL basics like SELECT, WHERE, GROUP BY, and JOIN. No Python experience is required.

Before We Start

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

Set Up Your Data Science Environment

Your SQL-to-Pandas notebook needs a working data science environment before you can write a single query. Python is the engine that runs your code.

Pandas gives you DataFrames for data manipulation. Jupyter Notebooks let you run code one cell at a time, making them perfect for comparing SQL and Pandas output side by side.

In this step, you will confirm your Python version, install the packages you need, and create your first Jupyter Notebook inside VS Code.

In this step, get ready to:
  • Confirm Python 3.11 or higher is installed.
  • Install pandas and the Jupyter kernel package.
  • Create your first Jupyter Notebook with a working Python kernel.
Check your Python version and install packages

Pandas 3.0 requires Python 3.11 or higher. Before installing any packages, you need to confirm your Python version meets this requirement.

  • Open the integrated terminal in VS Code by pressing Ctrl+` (backtick).
  • Check your Python version by running this command:

macOS

python3 --version

Windows

python --version

✔️ I see version 3.11 or higher

Your Python version is ready. Continue to the next action below.

ⓧ I see an older version

Your current Python version is below 3.11. Pandas 3.0 will not install on an older version.

  • Download Python 3.11 or higher from python.org/downloads.
  • Run the installer and follow the prompts.
  • On Windows, check the Add Python to PATH checkbox at the bottom of the installer before clicking Install Now.
  • Close and reopen the VS Code terminal, then run the version check again.

Still seeing the old version on macOS?

macOS may continue pointing python3 to the older system Python even after you install a newer version. Try running the version-specific command to check:

python3.11 --version

If that shows 3.11 or higher, use python3.11 instead of python3 for the rest of this project.

ⓧ Command not found

Python is not installed on your machine. Follow these steps to install it.

  • Download Python 3.11 or higher from python.org/downloads.
  • On macOS, open the downloaded .pkg file and follow the installer prompts.
  • On Windows, open the downloaded .exe file. Check Add Python to PATH at the bottom of the installer window, then click Install Now.
  • Close and reopen the VS Code terminal, then run the version check again to confirm.
  • Install pandas and the Jupyter kernel by running this command:
pip install pandas ipykernel

You should see pip downloading and installing several packages. The output ends with a line starting with "Successfully installed" followed by the package names.

Seeing 'command not found' for pip?

  • On macOS, try pip3 instead of pip. The pip3 command matches the python3 you used for the version check.
  • On Windows, make sure you checked Add Python to PATH during installation. Restart VS Code and try again.
  • If neither works, try running python3 -m pip install pandas ipykernel (macOS) or python -m pip install pandas ipykernel (Windows).

help me fix a pip command not found error when installing pandas

Install the Jupyter extension and create your first notebook

Jupyter Notebooks run inside VS Code through an extension. You need to install it once, then you can create and run notebooks directly from the editor.

  • Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
  • Type Jupyter in the search bar.
  • Find the Jupyter extension by Microsoft and click Install.

What is a Jupyter Notebook?

A Jupyter Notebook is a document made up of individual cells. Each cell can hold code, output, or text. You run each cell independently, which means you can see results one piece at a time.

This is perfect for data exploration. You will run a SQL query in one cell, see the results, then run the Pandas equivalent in the next cell and compare.

  • Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
  • Type Create: New Jupyter Notebook and select it from the dropdown.

A new untitled notebook opens in VS Code. Before writing any code, you need to tell the notebook which Python to use and save the file with a name.

  • Click Select Kernel in the top-right corner of the notebook.
  • Choose Python Environments from the dropdown.
  • Select your Python 3.11+ interpreter from the list.

What is a kernel?

A kernel is the process that runs your code behind the scenes. When you select a Python kernel, you are telling the notebook to use that specific Python installation for executing cells.

This is why you installed ipykernel earlier. It allows your Python environment to act as a Jupyter kernel.

  • Save your notebook by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • In the save dialog, navigate to your Desktop.
  • Name the file sql_to_pandas.ipynb and click Save.

Now that your kernel is connected and your file is saved, write your first cell to confirm everything works.

  • In the first cell of your notebook, type the following code:
# Cell 1: Imports and setup
import pandas as pd
import sqlite3

What does this code do?

  • import pandas as pd loads the pandas library and gives it the shorthand name pd. This is the standard convention in data science.
  • import sqlite3 loads Python's built-in SQLite module. You will use this to create an in-memory database and run SQL queries against it.

Before you run this cell, what do you expect to see if both packages are available? Take a moment to predict.

  • Press Shift+Enter to run the cell.

The cell execution counter on the left changes from [ ] to [1]. No error output appears below the cell. This confirms both pandas and sqlite3 imported successfully.

Seeing a ModuleNotFoundError?

  • If the error mentions pandas, the package installed into a different Python than the one your notebook is using. Click the kernel name in the top-right corner of the notebook, switch to the Python environment where you ran pip install, and re-run the cell.
  • If the error mentions sqlite3, your Python installation may be incomplete. Try reinstalling Python from python.org.

help me fix a ModuleNotFoundError for pandas in my Jupyter Notebook

✔️ Awesome, I've got everything!

Great. Double check you have saved sql_to_pandas.ipynb to your Desktop.

ⓧ I'd like to double check the full code

Your sql_to_pandas.ipynb should have exactly one cell with this code:

# Cell 1: Imports and setup
import pandas as pd
import sqlite3

Your data science environment is ready. You have Python, pandas, and a Jupyter Notebook running inside VS Code. Next up, you will build a SQL database inside Python and run your familiar queries against it.

Build Your SQL Database and Query It

Your data science environment is ready. You have Python, pandas, and Jupyter all working together inside VS Code.

Before jumping into Pandas syntax, you are going to run SQL queries inside Python first. This gives you a familiar anchor to compare against when you translate those same queries into Pandas in the next step.

In this step, get ready to:
  • Load a restaurant tipping dataset into Python and write it to a SQLite database.
  • Run SELECT, WHERE, and GROUP BY queries against the database using SQL you already know.
Create a SQLite database and load the tips dataset

The tips dataset has 244 rows of restaurant data including the total bill, tip amount, day of the week, and meal time. You will load it from the pandas GitHub repository and store it in an in-memory SQLite database so you can query it with SQL.

  • Click on the cell area below Cell 1 in your notebook, then click the + Code button to create a new cell.
  • Add the following code to load the tips dataset:
# Cell 2: Load the tips dataset
url = (
    "https://raw.githubusercontent.com/pandas-dev"
    "/pandas/main/pandas/tests/io/data/csv/tips.csv"
)
tips = pd.read_csv(url)
print(f"Loaded {len(tips)} rows")
tips.head()

What does this code do?

  • url stores the web address of a CSV file hosted on the pandas GitHub repository.
  • pd.read_csv(url) downloads the CSV file and loads it into a DataFrame called tips. A DataFrame is a table of rows and columns, similar to a SQL table or a spreadsheet.
  • tips.head() displays the first 5 rows so you can inspect the data at a glance.
  • Press Shift+Enter to run the cell.
  • You should see Loaded 244 rows printed, followed by a 5-row table with columns like total_bill, tip, sex, smoker, day, time, and size.

Seeing a connection error or empty table?

  • If you see a URL or connection error, make sure your computer is connected to the internet. The dataset is downloaded from GitHub.
  • If you see a NameError for pd, make sure Cell 1 (import pandas as pd) ran successfully before running this cell.

Help me debug my tips dataset loading error.

Now that you have data in Python, you need to put it into a SQL database so you can query it with the SQL syntax you already know.

  • Create a new cell below Cell 2 and add this code to build your SQLite database:
# Cell 3: Create SQLite database
conn = sqlite3.connect(":memory:")
tips.to_sql("tips", conn, index=False)

# Verify with a quick query
pd.read_sql_query("SELECT * FROM tips LIMIT 5", conn)

What does this code do?

  • sqlite3.connect(":memory:") creates a SQLite database that lives entirely in your computer's memory. No files are saved to disk.
  • tips.to_sql("tips", conn, index=False) writes every row of your DataFrame into a SQL table named tips.
  • pd.read_sql_query() runs a SQL query against the database and returns the results as a DataFrame. This is how you will run all SQL in this project.
  • Press Shift+Enter to run the cell.
  • You should see a 5-row table identical to the tips.head() output from the previous cell. This confirms the data made it into the SQLite database.

Seeing a NameError for tips or conn?

  • If tips is not defined, go back and re-run Cell 2 first. Each cell depends on the cells above it.
  • If sqlite3 is not defined, re-run Cell 1 which contains import sqlite3.

Help me fix my SQLite database setup.

Run familiar SQL queries against the database

You now have a SQL database loaded with real data inside Python. Time to run the queries you already know: SELECT for picking columns, WHERE for filtering rows, and GROUP BY for aggregation.

  • Create a new cell and add this SELECT query:
# Cell 4: SQL - SELECT specific columns
pd.read_sql_query("SELECT total_bill, tip, day FROM tips LIMIT 10", conn)
  • Press Shift+Enter to run the cell.
  • You should see a 10-row table with only three columns: total_bill, tip, and day.
  • Create another new cell and add this WHERE query:
# Cell 5: SQL - WHERE clause
pd.read_sql_query("SELECT * FROM tips WHERE time = 'Dinner'", conn)
  • Press Shift+Enter to run the cell.
  • You should see a table where every row has Dinner in the time column. Note the row count shown at the bottom of the output. You will use this number to verify your Pandas version in the next step.
  • Create one more cell and add this GROUP BY query:
# Cell 6: SQL - GROUP BY
pd.read_sql_query("""
    SELECT day, AVG(tip) AS avg_tip, COUNT(*) AS count
    FROM tips
    GROUP BY day
""", conn)

What does this query do?

This groups all 244 rows by the day column, then calculates two metrics for each group: the average tip (AVG(tip)) and the total number of rows (COUNT(*)).

The AS keyword gives each calculated column a readable name in the result.

Before you run this cell, think about what you expect. How many rows will the result have?

  • Press Shift+Enter to run the cell.

You should see 4 rows, one for each day in the dataset (Thur, Fri, Sat, Sun), with the average tip and row count for each day.

Not seeing the right output?

  • If a cell shows no output, make sure the last line of the cell is the pd.read_sql_query() call. Jupyter automatically displays the result of the last expression in a cell.
  • If you see an error about the tips table not existing, re-run Cell 3 to recreate the in-memory database. The database only exists while Python is running.
  • Make sure you are running cells in order (Cell 1, then Cell 2, then Cell 3, then Cells 4-6). Each cell depends on the ones above it.

Help me fix my SQL query output in Jupyter.

✔️ Awesome, I've got everything!

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

ⓧ I'd like to double check the full code

Scroll through your notebook and confirm you have the following 6 cells in order, each with output displayed below it:

  • Cell 1: Imports (import pandas as pd and import sqlite3).
  • Cell 2: Load the tips dataset with pd.read_csv(url). Output shows "Loaded 244 rows" and a 5-row table.
  • Cell 3: Create SQLite database with sqlite3.connect(":memory:") and verify with a SELECT * FROM tips LIMIT 5 query. Output shows a 5-row table.
  • Cell 4: SELECT query for total_bill, tip, day. Output shows 10 rows with 3 columns.
  • Cell 5: WHERE query filtering for Dinner. Output shows only dinner rows.
  • Cell 6: GROUP BY query with AVG(tip) and COUNT(*). Output shows 4 rows (one per day).

If any cell is missing or shows an error, re-create it using the code above and run it again.

Your SQL skills are working inside Python. You loaded a dataset, queried it with SELECT, WHERE, and GROUP BY, and saw familiar results. Next up, you will replicate each of these SQL queries in Pandas and compare the results side by side.

Replicate Your SQL in Pandas

Your SQL queries from the last step are already delivering results inside Python. You have working SELECT, WHERE, and GROUP BY output sitting right there in your notebook, ready to compare against.

Now you can learn Pandas by translating each SQL operation into its Pandas equivalent and immediately checking that the output matches. If the results are identical, you know the translation is correct.

In this step, get ready to:
  • Translate SQL SELECT and WHERE into Pandas column selection and boolean indexing.
  • Add calculated columns and use the Pandas equivalent of LIMIT.
Translate SELECT and WHERE to Pandas

In SQL, you pick columns with SELECT and filter rows with WHERE. Pandas has direct equivalents for both. Column selection uses double brackets, and filtering uses something called boolean indexing.

  • Create a new cell at the bottom of your notebook and type the following code:
# Cell 7: Pandas - SELECT (column selection)
tips[["total_bill", "tip", "day"]].head(10)
  • Run the cell by pressing Shift+Enter.

What does this code do?

tips[["total_bill", "tip", "day"]] selects three columns from the DataFrame. The double brackets return a DataFrame. This is the Pandas equivalent of SELECT total_bill, tip, day FROM tips.

Single brackets like tips["tip"] return a single column as a Series, not a DataFrame. Double brackets always return a DataFrame, even for one column.

  • Scroll up and compare this output to Cell 4 (your SQL SELECT query). The columns and values should be identical.

Now for filtering. In SQL, WHERE filters rows by a condition. In Pandas, you write the condition inside square brackets. The condition creates a True/False Series that picks which rows to keep.

  • Create a new cell and type this code:
# Cell 8: Pandas - WHERE (boolean indexing)
dinner = tips[tips["time"] == "Dinner"]
print(f"Dinner rows: {len(dinner)}")
dinner.head()

Before you run this cell, think about how many rows you expect. Your SQL WHERE query in Cell 5 filtered for dinner meals. Do you remember roughly how many rows that returned?

  • Run the cell with Shift+Enter.

You should see Dinner rows: 176 printed above the table. Scroll up to Cell 5 and count the rows from your SQL WHERE query. Both should show 176 dinner rows.

What does this code do?

tips["time"] == "Dinner" creates a Series of True/False values, one per row. Rows where the time column equals "Dinner" get True.

tips[...] keeps only the rows marked True. This is exactly what SQL WHERE does, just with different syntax. The result is stored in a variable called dinner so you can reuse it.

Seeing 0 rows?

  • Make sure you typed "Dinner" with a capital D. Python string comparisons are case-sensitive, unlike most SQL databases.
  • Check that you used == (double equals) for comparison, not = (single equals). Single equals assigns a value. Double equals checks for equality.
  • Why is my boolean indexing returning an empty DataFrame?

You can also combine multiple conditions, just like SQL's AND and OR operators.

  • Create a new cell and type this code to filter with two conditions:
# Cell 9: Pandas - Multiple conditions (AND/OR)
tips[(tips["time"] == "Dinner") & (tips["tip"] > 5)]
  • Run the cell with Shift+Enter.

How do combined conditions work?

  • & means AND (like SQL's AND). | means OR (like SQL's OR).
  • Each condition MUST be wrapped in parentheses. Without them, Python evaluates the operators in the wrong order and throws an error.
  • This is equivalent to SELECT * FROM tips WHERE time = 'Dinner' AND tip > 5.
  • Check the output. You should see a filtered table showing only dinner meals where the tip exceeded $5.

Seeing a TypeError?

  • If you see an error about ambiguous truth values, you are likely missing parentheses around each condition. Every condition needs its own pair of parentheses when combining with & or |.
  • My Pandas multiple condition filter is throwing an error. How do I fix it?
Translate calculated columns

SQL lets you create new columns with expressions like SELECT *, tip/total_bill AS tip_rate. Pandas has its own method for this called assign().

  • Create a new cell and type this code:
# Cell 10: Pandas - Calculated column
tips.assign(tip_rate=tips["tip"] / tips["total_bill"]).head()
  • Run the cell with Shift+Enter.

What does this code do?

  • tips.assign() creates a new column without modifying the original DataFrame. The original tips data stays untouched.
  • tip_rate=tips["tip"] / tips["total_bill"] divides tip by total_bill for every row and names the result tip_rate. This is the Pandas equivalent of SELECT *, tip/total_bill AS tip_rate.
  • Check the output. You should see the familiar columns plus a new tip_rate column on the right. Values will be decimals like 0.059 or 0.166, representing the tip as a fraction of the bill.

One last translation. SQL's LIMIT 10 has a direct Pandas equivalent you have already been using.

  • Create a new cell and type this code:
# Cell 11: Pandas - LIMIT equivalent
tips.head(10)
  • Run the cell with Shift+Enter.
  • You should see the first 10 rows of the full tips DataFrame. You already used .head(10) in Cell 7 combined with column selection. On its own, tips.head(10) is the direct equivalent of SELECT * FROM tips LIMIT 10.

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook.

ⓧ I'd like to double check the full code

Your notebook is longer than a single screen, so scroll through and confirm each of these cells exists in order:

  • Cell 1: Imports (import pandas as pd and import sqlite3).
  • Cell 2: Load the tips dataset with pd.read_csv(url).
  • Cell 3: Create SQLite database with sqlite3.connect(":memory:") and verify with a quick query.
  • Cell 4: SQL SELECT specific columns.
  • Cell 5: SQL WHERE clause.
  • Cell 6: SQL GROUP BY.
  • Cell 7: Pandas SELECT (column selection) with tips[["total_bill", "tip", "day"]].head(10).
  • Cell 8: Pandas WHERE (boolean indexing) with dinner = tips[tips["time"] == "Dinner"].
  • Cell 9: Pandas multiple conditions with & (AND).
  • Cell 10: Pandas calculated column with tips.assign(tip_rate=...).
  • Cell 11: Pandas LIMIT equivalent with tips.head(10).

You can now translate SELECT, WHERE, calculated columns, and LIMIT from SQL into Pandas. Every result matched. Next up, you'll tackle the most common SQL operation of all: GROUP BY. But Pandas has a sneaky gotcha waiting for you that SQL never had.

Grouping, Aggregation, and the Index Trap

Your Pandas toolkit is growing. You have translated SELECT, WHERE, calculated columns, and LIMIT from SQL to Pandas, and every result matched your SQL output.

GROUP BY is one of the most powerful SQL operations you use daily. Its Pandas equivalent, groupby, works differently in one crucial way that trips up every SQL user. In this step, you will hit that gotcha head-on, understand why it happens, and fix it.

In this step, get ready to:
  • Hit the groupby index trap and understand why Pandas behaves differently from SQL.
  • Fix the trap with reset_index() and confirm your results match your SQL GROUP BY output.
  • Build multi-metric aggregations with agg() and named aggregation syntax.
Translate GROUP BY to groupby and hit the index trap

The SQL GROUP BY you ran in Step 2 returned "day" as a regular column alongside your aggregated values. Pandas groupby performs the same operation, but the output looks different in a way you would not expect.

  • Add this code in a new cell in your notebook:
# Cell 12: Pandas - GROUP BY (the index trap!)
# Watch what happens to the "day" column:
result = tips.groupby("day")["tip"].mean()
print(type(result))
print(result)
# Notice: "day" is the INDEX, not a column!

Before you press Shift+Enter, take a guess. Will "day" show up as a regular column in the output, like it does in your SQL GROUP BY result?

  • Press Shift+Enter to run the cell.

The output shows the day names (Fri, Sat, Sun, Thur) on the left side, but they are not a column. They are the index. The print(type(result)) line confirms this is a Series, not a DataFrame. This does not happen in SQL.

What is the index trap?

Pandas uses the grouped column as the row label (the index) by default. In SQL, GROUP BY always returns the grouped column as a regular column you can reference. In Pandas, it disappears from the columns and becomes the index instead.

This is the most common gotcha for SQL users learning Pandas. Your data is still there, just hiding in the wrong place.

That confusing output is exactly what is supposed to happen. Now fix it.

  • Add this code in the next cell to fix the index trap with reset_index():
# Cell 13: Pandas - Fix the index trap
result = tips.groupby("day")["tip"].mean().reset_index()
result.columns = ["day", "avg_tip"]
print(result)
# Now "day" is a regular column again

What does this code do?

  • .reset_index() converts the index back into a regular column. This is the one-method fix for the index trap.
  • result.columns = ["day", "avg_tip"] renames the columns to match your SQL output, where you used AS avg_tip.
  • Press Shift+Enter to run the cell.
  • Scroll up to your Cell 6 SQL GROUP BY result. Compare the day values and average tips. Both outputs should show the same four days with the same averages.

Output still looks wrong?

  • If "day" is still in the index, check that you chained .reset_index() directly after .mean() on the same line. Calling it separately on the next line does not work unless you assign the result first.
  • If you see a NameError for tips, scroll up and re-run Cell 2 to reload the DataFrame.

Why is my groupby output missing the 'day' column?

Build multi-metric aggregations with agg()

In SQL, you can compute multiple aggregate functions in a single GROUP BY query. Your Cell 6 SQL query calculated both AVG(tip) and COUNT(*) at once. Pandas has agg() for exactly this purpose.

  • Add this code in a new cell to run multiple aggregations with named output columns:
# Cell 14: Pandas - Multi-metric aggregation
tips.groupby("day").agg(
    avg_tip=("tip", "mean"),
    total_bill_sum=("total_bill", "sum"),
    count=("tip", "size")
).reset_index()

What does this code do?

  • .agg() accepts named aggregation tuples. Each argument like avg_tip=("tip", "mean") means "compute the mean of the tip column and name the result avg_tip."
  • count=("tip", "size") uses size to count all rows per group, the same as SQL COUNT(*).
  • .reset_index() at the end keeps "day" as a regular column. You already know why.
  • Press Shift+Enter to run the cell.
  • You should see a clean DataFrame with four columns: day, avg_tip, total_bill_sum, and count. Compare the avg_tip and count values to your Cell 6 SQL result. They should match.

There is one more SQL-to-Pandas difference hiding in this aggregation. In SQL, COUNT(*) and COUNT(column) behave differently. Pandas has the same distinction, but the method names are not obvious.

  • Add this code in a new cell to see the difference between size() and count():
# Cell 15: size() vs count()
# size() counts all rows (like SQL COUNT(*))
print("size():", tips.groupby("day").size())
print()
# count() counts non-null values per column (like SQL COUNT(column))
print("count():", tips.groupby("day")["tip"].count())

What is the difference between size() and count()?

  • size() counts every row in each group, regardless of null values. This is the Pandas equivalent of SQL COUNT(*).
  • count() counts only non-null values in the specified column. This is the Pandas equivalent of SQL COUNT(column).

In this dataset the numbers are identical because there are no null tip values. In real-world data with missing values, the distinction matters.

  • Press Shift+Enter to run the cell.
  • You should see two outputs. Both show the same counts for each day (Fri, Sat, Sun, Thur) because the tips dataset has no null values in the tip column.

Seeing unexpected output from groupby?

  • If you see a KeyError for "day" or "tip", check that the column names in your code are lowercase and match the dataset exactly.
  • If size() returns a DataFrame instead of a Series, make sure you called .size() directly on the GroupBy object, not on a column selection.

Help me debug my Pandas groupby aggregation code.

✔️ Awesome, I've got everything!

Great. Make sure your notebook is saved by pressing Cmd+S (macOS) or Ctrl+S (Windows).

ⓧ I'd like to double check the full code

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

  • Cell 12 (the index trap): tips.groupby("day")["tip"].mean() with print(type(result)) and print(result). Output shows "day" in the index.
  • Cell 13 (the fix): .reset_index() with column rename. Output shows "day" and "avg_tip" as regular columns.
  • Cell 14 (multi-metric aggregation): .agg() with named aggregations for avg_tip, total_bill_sum, and count. Output is a clean four-column DataFrame.
  • Cell 15 (size vs count): size() and count() side by side. Both show the same values per day.

You have now translated every core SQL operation into Pandas. SELECT, WHERE, GROUP BY, calculated columns, and multi-metric aggregations all have Pandas equivalents, and you have seen the results match. Your notebook is becoming a reusable SQL-to-Pandas cheat sheet. Up next, tackle the Secret Mission and bring SQL JOINs into Pandas.

Secret mission

Joins with merge()

You have translated SELECT, WHERE, and GROUP BY from SQL to Pandas. But what about JOINs? In this mission, you will create two related DataFrames, join them with pd.merge(), and discover a surprising difference in how Pandas treats null keys compared to SQL.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your machine, so there are no ongoing costs. Decide whether to keep your resources, pause them to come back later, or delete them entirely.

Resources you used:

  • sql_to_pandas.ipynb notebook file.
  • pandas and ipykernel Python packages.
  • Jupyter VS Code extension.

Keep everything running

No action needed. Choose this if you plan to keep exploring data analysis with Python.

  • Your sql_to_pandas.ipynb notebook stays on your machine as a reusable SQL-to-Pandas reference.
  • The pandas and ipykernel packages remain installed for future data science projects.
  • The Jupyter extension stays in VS Code, ready for your next notebook.

Pause - I'll come back to this later

Since this project runs entirely locally, there are no running processes or cloud services to pause.

  • Your notebook file and Python packages remain on your machine until you decide to remove them.
  • No action needed. Everything is saved as files and installed packages with no ongoing cost.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Uninstall the Python packages by running this command in your terminal:
pip uninstall pandas ipykernel

Type y and press Enter when prompted to confirm each package removal.

  • Delete the sql_to_pandas.ipynb file. In VS Code, right-click the file tab and select Reveal in Finder (macOS) or Reveal in File Explorer (Windows), then move the file to your Trash or Recycle Bin.
  • Uninstall the Jupyter extension. In VS Code, open the Extensions panel with Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows), search for Jupyter, and click Uninstall.

Nice Work!

Nice Work!

You just built a complete SQL-to-Pandas cheat sheet inside a Jupyter Notebook. Every query you knew in SQL now has a working Pandas equivalent sitting right next to it, ready to reference on your next data project.

You've learned how to:

  • Query a SQLite database using SQL inside Python, proving your existing SQL skills transfer directly into a data science workflow.
  • Translate SELECT, WHERE, GROUP BY, and calculated columns from SQL to Pandas using column selection, boolean indexing, groupby, and assign.
  • Discover and fix the groupby index trap with reset_index(), and build multi-metric aggregations with agg() and named aggregations for clean, readable output.
  • Secret Mission: Complete an optional challenge to push your skills further.

Ready to quiz yourself?