Case Study 1: Priya Automates the Weekly Regional Sales Summary

Chapter 5 — Loops and Iteration: Automating Repetitive Tasks Python for Business for Beginners


Background

Every Monday morning at 8:45 a.m., Priya Okonkwo sits down at her desk and starts the same ritual: open four spreadsheet tabs — one per Acme Corp region — copy this week's figures into a master summary, calculate the totals, format the table, and email it to Sandra Chen before the 9:30 all-hands.

The task takes forty minutes. It requires almost no judgment. And Priya has to do it fifty-two times a year.

She has started making errors — not often, but enough to notice. Last month she pasted a column of figures one row off and the West Coast numbers were inflated by $14,000 before Sandra caught it. The conversation was polite, but the implication was clear: this workflow is not scaling.

Marcus Webb has suggested automating it three times. Each time, Priya has nodded and changed the subject. She knows how to use Excel. She does not know how to code.

Until now.


The Data

Priya is working with five weeks of sales figures across four regions. In the real version she pulls these from a spreadsheet; here they live directly in Python as a 2D list. (Chapter 9 covers pulling them from a file, and Chapter 16 covers reading them directly from Excel.)

regions = ["Northeast", "Southeast", "Midwest", "West Coast"]

# Five weeks of sales data. Each inner list is one week across all four regions.
# Order within each week matches the `regions` list above.
weekly_sales = [
    [145_200, 98_400, 112_600, 187_300],   # Week 1
    [152_800, 103_700, 108_900, 201_400],  # Week 2
    [138_500, 91_200, 119_300, 195_600],   # Week 3
    [161_300, 107_800, 124_500, 215_700],  # Week 4
    [158_700, 112_300, 131_200, 208_900],  # Week 5
]

weekly_targets = [150_000, 100_000, 115_000, 200_000]

Step 1 — Total Sales Per Region

Priya's first task is to get each region's five-week cumulative total. She learned about zip() in Chapter 5: it lets her combine two parallel sequences. Here she uses zip(*weekly_sales) — the asterisk "unpacks" the outer list so zip can group by column rather than by row.

regional_totals = {}

for region, weekly_figures in zip(regions, zip(*weekly_sales)):
    regional_totals[region] = sum(weekly_figures)
    print(f"  {region}: ${regional_totals[region]:,.0f}")

Output:

  Northeast:  $756,500
  Southeast:  $513,400
  Midwest:    $596,500
  West Coast: $1,008,900

The zip(*weekly_sales) idiom is worth pausing on. weekly_sales is a list of lists — five rows, each with four figures. Using * unpacks it into five separate arguments to zip. The result groups all Week-1-through-5 figures for the Northeast together, all figures for the Southeast together, and so on. It is a transpose operation without importing anything.


Step 2 — Running Totals Week by Week

The cumulative picture is useful, but Sandra actually wants to see momentum: is revenue growing, holding steady, or declining week over week? Priya uses enumerate() to track the week number alongside the figures.

print("\nCumulative Revenue by Week")
print("-" * 50)

running_total = 0

for week_num, figures in enumerate(weekly_sales, start=1):
    week_total = sum(figures)
    running_total += week_total
    print(f"  Week {week_num}: ${week_total:>10,.0f}  |  Running total: ${running_total:>12,.0f}")

Output:

Cumulative Revenue by Week
--------------------------------------------------
  Week 1: $  543,500  |  Running total: $      543,500
  Week 2: $  566,800  |  Running total: $    1,110,300
  Week 3: $  544,600  |  Running total: $    1,654,900
  Week 4: $  609,300  |  Running total: $    2,264,200
  Week 5: $  611,100  |  Running total: $    2,875,300

The pattern here — running_total += week_total — is called an accumulator. You initialize a variable to zero before the loop, then add to it with each iteration. At the end of the loop, it holds the grand total.


Step 3 — Best and Worst Performers

Sandra always asks the same question in the Monday meeting: "Who's leading and who needs support?" Priya wants to identify the top- and bottom-performing regions automatically rather than scanning numbers by eye.

best_region = ""
best_total = 0

worst_region = ""
worst_total = float("inf")   # Start with positive infinity so any real value is smaller

for region, total in regional_totals.items():
    if total > best_total:
        best_total = total
        best_region = region

    if total < worst_total:
        worst_total = total
        worst_region = region

print(f"\nTop performer:    {best_region} (${best_total:,.0f})")
print(f"Needs attention:  {worst_region} (${worst_total:,.0f})")

Output:

Top performer:    West Coast ($1,008,900)
Needs attention:  Southeast ($513,400)

The float("inf") initialization is a reliable trick. When you want to find a minimum value by looping, starting with positive infinity guarantees that the very first real value will be smaller and replace it. Similarly, starting best_total at zero guarantees the first positive sales figure wins.


Step 4 — Target vs. Actual

Every region has a monthly target, and Priya wants to show Sandra not just the raw numbers but whether each region hit its goal. She uses zip() to pair regions, totals, and targets simultaneously, and a list comprehension to calculate the variance figures.

# Calculate the per-period target (5 weeks, so multiply weekly target by 5)
period_targets = [target * 5 for target in weekly_targets]

variance_data = [
    (region, regional_totals[region], target,
     regional_totals[region] - target,
     ((regional_totals[region] - target) / target) * 100)
    for region, target in zip(regions, period_targets)
]

print("\nTarget vs. Actual Performance")
print("-" * 60)
print(f"  {'Region':<14} {'Actual':>10} {'Target':>10} {'Variance':>12} {'% vs Target':>12}")
print("-" * 60)

for region, actual, target, variance, variance_pct in variance_data:
    sign = "+" if variance >= 0 else ""
    print(
        f"  {region:<14} "
        f"${actual:>9,.0f} "
        f"${target:>9,.0f} "
        f"${variance:>+10,.0f} "
        f"  {sign}{variance_pct:.1f}%"
    )

Output:

Target vs. Actual Performance
------------------------------------------------------------
  Region         Actual      Target     Variance   % vs Target
------------------------------------------------------------
  Northeast    $756,500   $750,000      +$6,500      +0.9%
  Southeast    $513,400   $500,000     +$13,400      +2.7%
  Midwest      $596,500   $575,000     +$21,500      +3.7%
  West Coast $1,008,900 $1,000,000      +$8,900      +0.9%

Step 5 — The Formatted Report

Now Priya combines everything into a single function that generates the full report. This is what she will eventually schedule to run automatically every Monday at 8:30 a.m.

def generate_weekly_summary(regions, weekly_sales, weekly_targets, week_labels=None):
    """
    Generate a formatted weekly regional sales summary.

    Parameters
    ----------
    regions      : list of region name strings
    weekly_sales : list of lists; each inner list is one week of regional figures
    weekly_targets: list of weekly target amounts, aligned to regions
    week_labels  : optional list of label strings for each week
    """
    if week_labels is None:
        week_labels = [f"Week {i}" for i in range(1, len(weekly_sales) + 1)]

    num_weeks = len(weekly_sales)
    period_targets = [t * num_weeks for t in weekly_targets]

    # Compute regional totals
    regional_totals = {
        region: sum(figures)
        for region, figures in zip(regions, zip(*weekly_sales))
    }

    grand_total = sum(regional_totals.values())

    # Header
    print("=" * 65)
    print("  ACME CORP — WEEKLY REGIONAL SALES SUMMARY")
    print(f"  Period: {week_labels[0]} through {week_labels[-1]}")
    print("=" * 65)

    # Week-by-week breakdown
    print("\nWEEK BY WEEK (ALL REGIONS COMBINED)")
    print("-" * 65)
    running_total = 0
    prev_week_total = None

    for week_label, figures in zip(week_labels, weekly_sales):
        week_total = sum(figures)
        running_total += week_total

        if prev_week_total is not None:
            change = week_total - prev_week_total
            change_str = f"  ({'+' if change >= 0 else ''}{change:,.0f})"
        else:
            change_str = "  (baseline)"

        print(f"  {week_label:<10} ${week_total:>10,.0f}{change_str}")
        prev_week_total = week_total

    print("-" * 65)
    print(f"  {'TOTAL':<10} ${grand_total:>10,.0f}")

    # Regional breakdown
    print("\nREGIONAL PERFORMANCE vs. TARGET")
    print("-" * 65)
    print(f"  {'Region':<14} {'Actual':>10} {'Target':>10} {'vs. Target':>12}")
    print("-" * 65)

    best_region = max(regional_totals, key=regional_totals.get)
    worst_region = min(regional_totals, key=regional_totals.get)

    for region, period_target in zip(regions, period_targets):
        actual = regional_totals[region]
        variance = actual - period_target
        flag = " <-- TOP" if region == best_region else (" <-- WATCH" if region == worst_region else "")
        print(
            f"  {region:<14} ${actual:>9,.0f} ${period_target:>9,.0f} "
            f"${variance:>+10,.0f}{flag}"
        )

    print("=" * 65)
    print(f"  Grand Total: ${grand_total:>10,.0f}   |   {num_weeks}-week period")
    print("=" * 65)


# Run it
week_labels = ["Oct W1", "Oct W2", "Oct W3", "Oct W4", "Oct W5"]
generate_weekly_summary(regions, weekly_sales, weekly_targets, week_labels)

Output:

=================================================================
  ACME CORP — WEEKLY REGIONAL SALES SUMMARY
  Period: Oct W1 through Oct W5
=================================================================

WEEK BY WEEK (ALL REGIONS COMBINED)
-----------------------------------------------------------------
  Oct W1     $  543,500  (baseline)
  Oct W2     $  566,800  (+23,300)
  Oct W3     $  544,600  (-22,200)
  Oct W4     $  609,300  (+64,700)
  Oct W5     $  611,100  (+1,800)
-----------------------------------------------------------------
  TOTAL      $2,875,300

REGIONAL PERFORMANCE vs. TARGET
-----------------------------------------------------------------
  Region         Actual     Target    vs. Target
-----------------------------------------------------------------
  Northeast    $756,500   $750,000     +$6,500
  Southeast    $513,400   $500,000    +$13,400
  Midwest      $596,500   $575,000    +$21,500
  West Coast $1,008,900 $1,000,000     +$8,900 <-- TOP
=================================================================
  Grand Total: $2,875,300   |   5-week period
=================================================================

What Priya Learned

This project took Priya about three hours to write and test — a little more than twice the time the manual process takes each week. But every Monday after this one, the process takes ten seconds: run the script.

More importantly, she learned something that's harder to articulate than a specific function: she learned to see data as a sequence of operations rather than a collection of cells. The zip(*weekly_sales) line would have been opaque to her two chapters ago. Now it reads like a sentence.

When she showed Marcus the script, he laughed. "That's cleaner than the Excel macro," he said. "Want me to set up a cron job so it runs automatically?"

Priya had no idea what a cron job was. She said yes.


Key Concepts Demonstrated

Concept Where Used
for loop over a list of lists Weekly totals calculation
enumerate() Week numbering in the running-total loop
zip() Pairing regions with figures
zip(*iterable) Transposing rows to columns
Accumulator pattern running_total variable
float("inf") initialization Finding the minimum value safely
List comprehension Variance data table
f-string formatting All output lines
Named function with parameters generate_weekly_summary()

Challenge Extension

Once you have finished Chapter 5, extend this script:

  1. Add a week-over-week change column to the regional table (not just the grand total).
  2. Use a nested loop to print every individual region-week cell in a grid layout.
  3. Use continue to skip any week where a region's figures are None (representing a data gap).
  4. Calculate and display the standard deviation of weekly totals using only loops and arithmetic (no external libraries).