Lesson 11 of 16 · Level 3: Advanced

Data Analysis with pandas: From Excel to Python

  • ⏱ 12 min read
  • ❓ 4 review questions
  • ✍️ Exercise with solution
  • 📥 Downloadable template
In this lesson
  1. Setup
  2. DataFrames and Series
  3. Selecting and filtering
  4. Creating columns and cleaning data
  5. Summarizing: groupby and pivot tables
  6. Joining tables: pandas' XLOOKUP
  7. Exporting and charting
  8. Real case: a messy e-commerce export
  9. Common pandas mistakes
  10. Summary
What you'll learn
  • Load CSV and Excel data into a DataFrame and explore it.
  • Filter, sort, create columns and clean data.
  • Summarize with groupby and pivot tables, and join tables with merge.
  • Export results to Excel and make a quick chart.
  • Clean a messy e-commerce export (dollar signs, commas, US dates) and read pandas' long error messages.
📥 Practice this lesson

If you'd rather not generate the data with code, download the file and start straight from pd.read_csv("sales.csv").

  • sales.csv · first-quarter sales for a small coffee roaster, ready to use

pandas is the go-to library for analyzing data with Python. It does what Excel does (filters, sorting, pivot tables, XLOOKUP) but with millions of rows, automatically and repeatably: write the analysis once and rerun it every month. If you've taken our spreadsheets for data analysis lesson, you'll recognize every concept; if not, that's fine, we start from scratch.

  1. Load the CSV or Excel file
  2. Explore with info()
  3. Filter and clean
  4. Summarize with groupby
  5. Export and chart

If you're coming from Excel, here's the "translation guide" you'll use all lesson long:

FILTER → df[condition]

Keep the rows that meet one or more conditions.

SUMIFS → groupby

Totals, counts and averages by group.

PivotTable → pivot_table

Rows, columns and values, just like Excel's PivotTable pane.

XLOOKUP → merge

Bring in data from another table using a shared column.

Setup

pip install pandas openpyxl matplotlib

Install them inside your virtual environment, as you learned in lesson 10. openpyxl lets pandas read and write Excel files, and matplotlib draws charts. Google Colab already has all three.

To practice, this code creates the sample sales file: first-quarter orders for a small Portland coffee roaster that sells coffee, tea and chocolate to cafés through three reps:

import pandas as pd

data = {
    "date": ["2026-01-05", "2026-01-12", "2026-01-20", "2026-02-03", "2026-02-15",
             "2026-02-21", "2026-03-02", "2026-03-10", "2026-03-18", "2026-03-25"],
    "region": ["West", "East", "West", "Midwest", "East", "West", "Midwest", "East", "West", None],
    "rep": ["Emily", "Jake", "Emily", "Maria", "Jake", "Emily", "Maria", "Jake", "Emily", "Maria"],
    "product": ["Coffee", "Tea", "Coffee", "Coffee", "Tea", "Chocolate", "Coffee", "Tea", "Chocolate", "Coffee"],
    "units": [120, 85, 100, 60, 110, 40, 75, 90, 55, 70],
    "price": [14.0, 9.5, 14.0, 14.0, 9.5, 16.0, 14.0, 9.5, 16.0, 14.0],
}
pd.DataFrame(data).to_csv("sales.csv", index=False)

DataFrames and Series

A DataFrame is a table: named rows and columns, like a worksheet. Each column is a Series.

df = pd.read_csv("sales.csv", parse_dates=["date"])
# For Excel: pd.read_excel("sales.xlsx", sheet_name="Data")

print(df.head())        # first 5 rows
print(df.shape)         # (10, 6): rows and columns
df.info()               # columns, types and non-null values
print(df.describe())    # statistics for numeric columns

Exploring first is rule number one. Here's the key part of what info() shows:

 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   date     10 non-null     datetime64[us]
 1   region   9 non-null      str
 2   rep      10 non-null     str
 3   product  10 non-null     str
 4   units    10 non-null     int64
 5   price    10 non-null     float64

The region column has 9 values for 10 rows: one is missing. We'll fix that in a moment.

Selecting and filtering

df["rep"]                              # one column
df[["rep", "units"]]                   # several columns (double brackets)

df[df["region"] == "West"]                                  # filter rows
df[(df["region"] == "West") & (df["units"] > 100)]         # AND
df[(df["product"] == "Tea") | (df["product"] == "Chocolate")] # OR
df[df["product"].isin(["Tea", "Chocolate"])]                # same thing, shorter

df.sort_values("units", ascending=False).head(3)            # top 3

Each condition goes in parentheses, and you combine them with & (and) and | (or). It's the same logic as Excel's FILTER function.

1. How do you filter East region sales with more than 85 units?

  • df[df["region"] == "East" and df["units"] > 85]
  • df.filter("East", 85)
  • df[(df["region"] == "East") & (df["units"] > 85)]

pandas uses & (not and), and each condition goes in parentheses. With this data you get Jake's two tea orders of 110 and 90 units.

Creating columns and cleaning data

df["revenue"] = df["units"] * df["price"]           # calculated column
df["month"] = df["date"].dt.to_period("M")          # 2026-01, 2026-02...

df["region"] = df["region"].fillna("Unknown")       # fill blanks
df["rep"] = df["rep"].str.strip().str.title()       # clean text
df = df.drop_duplicates()                           # remove duplicate rows

Operations apply to the whole column at once, with no loops. That's much faster and easier to read than walking through rows with for, and it's the biggest mindset shift from lesson 5.

Conditional columns

For the equivalent of Excel's IF, use numpy.where (numpy is installed with pandas):
import numpy as np
df["size"] = np.where(df["revenue"] >= 1000, "Large", "Regular")

Summarizing: groupby and pivot tables

df.groupby("rep")["revenue"].sum().sort_values(ascending=False)

df.groupby("region").agg(
    sales=("revenue", "sum"),
    orders=("revenue", "count"),
    avg_order=("revenue", "mean"),
)

pd.pivot_table(df, values="revenue", index="product",
               columns="month", aggfunc="sum", fill_value=0)
rep
Emily    4600.0
Maria    2870.0
Jake     2707.5
Name: revenue, dtype: float64

groupby is pandas' SUMIFS; pivot_table is the PivotTable. The first result shows Emily sold $4,600, Maria $2,870 and Jake $2,707.50. Group by product instead (df.groupby("product")["revenue"].sum()) and you'll see where the $10,177.50 total comes from:

Coffee $5,950, tea $2,707.50 and chocolate $1,520: more than half the business depends on a single product, which is key information before negotiating with suppliers.

2. What does df.groupby("product")["units"].sum() do?

  • Adds up the units sold for each product.
  • Sorts the products alphabetically.
  • Counts how many different products there are.

It groups the rows by product and sums the units column in each group: 95 chocolate, 425 coffee and 285 tea.

Joining tables: pandas' XLOOKUP

targets = pd.DataFrame({"rep": ["Emily", "Jake", "Maria"],
                        "target": [4000, 3000, 2500]})

summary = df.groupby("rep", as_index=False)["revenue"].sum()
summary = summary.merge(targets, on="rep", how="left")
summary["attainment"] = (summary["revenue"] / summary["target"]).round(3)
print(summary)
     rep  revenue  target  attainment
0  Emily   4600.0    4000       1.150
1   Jake   2707.5    3000       0.902
2  Maria   2870.0    2500       1.148

merge joins on a shared column. With how="left", every row of the left table is kept even if it finds no match (like an XLOOKUP that returns blank). Emily hit 115% of her quarterly target, Maria 114.8%, and Jake fell short at 90.2%.

Exporting and charting

summary.to_excel("sales_summary.xlsx", index=False)    # to Excel
summary.to_csv("sales_summary.csv", index=False)       # to CSV

import matplotlib.pyplot as plt

by_month = df.groupby("month")["revenue"].sum()
by_month.plot(kind="bar", title="Sales by month")
plt.ylabel("Revenue ($)")
plt.tight_layout()
plt.savefig("sales_by_month.png")    # or plt.show() to see it on screen

You'll go much deeper into charts in lesson 14. For now, notice how little code it takes to go from raw CSV to an Excel file and an image you can drop into an email.

Real case: a messy e-commerce export

An online candle shop in Asheville, North Carolina, exports its March orders from its store platform. The amounts come as text with dollar signs and commas ("$1,250.50"), the dates are in U.S. month/day/year format, and one channel was typed two ways ("Instagram" and "instagram "). It's the kind of file you'll get from Shopify, Square or Etsy every week, and pandas handles it with the right steps. To keep the example self-contained, the CSV lives inside the code via io.StringIO; with a real file you'd pass its name instead.

import io
import pandas as pd

# March orders from an online candle shop in Asheville, NC, exported from its store platform
csv_export = """order,date,channel,amount
1001,03/03/2026,Instagram,"$1,250.50"
1002,03/03/2026,Website,$389.90
1003,03/04/2026,Etsy,$720.00
1004,03/05/2026,Instagram,"$2,100.00"
1005,03/05/2026,Website,"$1,045.30"
1006,03/06/2026,instagram ,$560.00
"""
df = pd.read_csv(io.StringIO(csv_export))
df["amount"] = df["amount"].str.replace("[$,]", "", regex=True).astype(float)
df["date"] = pd.to_datetime(df["date"], format="%m/%d/%Y")
df["channel"] = df["channel"].str.strip().str.lower()

summary = df.groupby("channel")["amount"].agg(["count", "sum", "mean"]).round(2)
summary = summary.sort_values("sum", ascending=False)
print(summary)
print(f"Total: ${df['amount'].sum():,.2f}")
print(f"Instagram: {summary.loc['instagram', 'sum'] / df['amount'].sum():.1%} of sales")

Output:

           count     sum    mean
channel
instagram      3  3910.5  1303.5
website        2  1435.2   717.6
etsy           1   720.0   720.0
Total: $6,065.70
Instagram: 64.5% of sales
  • .str.replace("[$,]", "", regex=True) removes every dollar sign and comma in one pass (the brackets mean "either of these characters"), and .astype(float) turns "1250.50" into a number. Without it, astype(float) fails with ValueError: could not convert string to float: '$1,250.50'.
  • format="%m/%d/%Y" makes the month/day order explicit, so "03/04/2026" is always March 4, never April 3.
  • Clean before grouping: without .str.strip().str.lower(), "Instagram" and "instagram " would be two channels and the report would be split.

The business takeaway: Instagram brings in 64.5% of sales and the highest average order ($1,303.50), so it deserves the most attention.

3. A supplier in Germany sends you a CSV whose columns are separated by semicolons. How do you read it?

  • pd.read_csv("file.csv")
  • pd.read_csv("file.csv", sep=";")
  • pd.read_excel("file.csv")

The sep parameter sets the column separator. By default pandas expects commas; much of Europe uses semicolons (and decimal="," for numbers like 1.250,50).

4. Your amount column contains text like "$1,250.50". Which line turns it into numbers?

  • df["amount"].astype(float)
  • df["amount"].str.replace("[$,]", "", regex=True).astype(float)
  • pd.to_numeric(df["amount"])

Both astype(float) and pd.to_numeric fail on the dollar sign and the comma. You have to remove those characters first, then convert.

Common pandas mistakes

pandas errors are long because they include the library's internal code. If you ask for a column that doesn't exist, you'll see dozens of lines and, halfway through, The above exception was the direct cause of the following exception. The useful information is in two places:

Traceback (most recent call last):
  File "report.py", line 3, in <module>
    print(df["Region"])
          ~~^^^^^^^^^^
  ...
KeyError: 'Region'

The first line that mentions your file (report.py, line 3) tells you where the problem is; the last line tells you what happened: there's no 'Region' column (it's region, lowercase). Skip the lines in between that point into pandas/core/….

Misspelling a column name: df["Region"].

Names are case- and space-sensitive. Check print(df.columns); if they have stray spaces, clean them with df.columns = df.columns.str.strip().

Using and or or to combine filters.

Use & and |, with each condition in parentheses.

Leaving "$1,250.50" as text and trying to sum it.

Strip the symbols with .str.replace(), then .astype(float).

A CSV that loads into a single column.

It's separated by semicolons or tabs: add sep=";" or sep="\t".

Checklist before sending a pandas report

Practice exercise: an automated report

Using the sample sales file (or your own), write a script report.py that:

  1. Loads the data, fills in missing regions and calculates revenue.
  2. Calculates sales by region and by product.
  3. Finds the best month and the rep with the highest average order.
  4. Saves an Excel file with two sheets (region and product) and a PNG chart.
See solution
import pandas as pd

df = pd.read_csv("sales.csv", parse_dates=["date"])
df["region"] = df["region"].fillna("Unknown")
df["revenue"] = df["units"] * df["price"]
df["month"] = df["date"].dt.to_period("M")

by_region = df.groupby("region")["revenue"].sum().sort_values(ascending=False)
by_product = df.groupby("product")["revenue"].sum().sort_values(ascending=False)
best_month = df.groupby("month")["revenue"].sum().idxmax()
best_avg = df.groupby("rep")["revenue"].mean().idxmax()

print(f"Best month: {best_month} · Highest average order: {best_avg}")

with pd.ExcelWriter("report.xlsx") as writer:
    by_region.to_excel(writer, sheet_name="Region")
    by_product.to_excel(writer, sheet_name="Product")

ax = by_region.plot(kind="barh", title="Sales by region")
ax.figure.tight_layout()
ax.figure.savefig("sales_by_region.png")

With the sample data it prints Best month: 2026-01 · Highest average order: Emily (January brought in $3,887.50, and Emily averages $1,150 per order).

Summary

  • pd.read_csv and pd.read_excel load data; head, info and describe explore it.
  • Filter with conditions in parentheses joined by & and |; create columns by operating on whole columns.
  • groupby and pivot_table summarize; merge joins tables like XLOOKUP.
  • to_excel, to_csv and plot share the results.
  • Clean real exports before analyzing: strip symbols, set date formats and normalize text.

Next lesson: Python APIs and Automation: requests, Files and Reports. You'll connect your programs to the outside world with web APIs and JSON, and automate everyday tasks.

🎓 Save your progress and get a free certificate

Create your account in 30 seconds, mark your lessons and download your certificate at the end.

Enroll for free