EduFlow

Section 1 · Python Essentials for Analysts

Loops, Conditions and Comprehensions

18 min3 min readPython exercise

Lesson at a glance

Process every row of a dataset with for loops, make decisions with if, elif and else, count with dictionaries, and write the same logic compactly as comprehensions.

  1. 1The for loop
  2. 2Making decisions with if, elif, else
  3. 3Counting and summing by group
  4. 4Comprehensions: building lists in one line
  5. 5Loops versus pandas

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Six rows from Cartwheel’s messy export sit in a list of dictionaries. You want their total value, how much came through each sales channel, and which orders were large. Writing one line per row would work for six rows and collapse at six thousand. A loop writes the logic once and Python repeats it for every row.

The for loop

Python
orders = [
    {"order_id": 2574, "total": 65.0, "channel": "mobile app"},
    {"order_id": 2069, "total": 18.0, "channel": "mobile app"},
    {"order_id": 1200, "total": 33.0, "channel": "mobile app"},
    {"order_id": 1809, "total": 35.0, "channel": "web"},
    {"order_id": 1434, "total": 72.0, "channel": None},
    {"order_id": 2089, "total": 58.0, "channel": "marketplace"},
]

grand_total = 0
for order in orders:
    grand_total += order["total"]
print(grand_total)

Read it as “for each order in orders, run the indented lines”. On each pass, the name order points at the next dictionary. grand_total += order["total"] is short for grand_total = grand_total + order["total"].

Indentation is not decoration in Python: the indented lines (four spaces, by convention) are the loop’s body, and the first line back at the left margin runs after the loop finishes. That is why print sits outside: you want one result, not six.

Making decisions with if, elif, else

Conditions let each row take a different path. Cartwheel’s operations team labels orders by size:

Python
totals = [65.0, 18.0, 33.0, 35.0, 72.0, 58.0, 329.0]

for t in totals:
    if t < 50:
        size = "small"
    elif t < 150:
        size = "medium"
    else:
        size = "large"
    print(t, size)

Python tests the conditions from top to bottom and runs only the first branch that is true. Order matters: if the 150 test came first, every small order would be labelled medium too, because 18 is also below 150. Put the narrowest condition first, or make the conditions non-overlapping.

You can combine conditions with and, or and not. Python also lets you chain comparisons the way you would write them in maths, which is handy for ranges:

Python
t = 89.0
channel = "web"
print(t >= 50 and channel == "web")   # both must be true
print(50 <= t < 150)                  # chained: at least 50 and below 150
print(not channel == "marketplace")   # flips True and False

When you also need each item’s position, for a ranked list or a “row 3 is broken” message, wrap the list in enumerate(), which hands you the position and the item together: for position, t in enumerate(totals, start=1):. Prefer it to counting positions by hand with a separate variable.

Counting and summing by group

The single most useful loop pattern in analysis is “per category, add something up”. A dictionary holds one running total per key:

Python
orders = [
    {"order_id": 2574, "total": 65.0, "channel": "mobile app"},
    {"order_id": 2069, "total": 18.0, "channel": "mobile app"},
    {"order_id": 1200, "total": 33.0, "channel": "mobile app"},
    {"order_id": 1809, "total": 35.0, "channel": "web"},
    {"order_id": 1434, "total": 72.0, "channel": None},
    {"order_id": 2089, "total": 58.0, "channel": "marketplace"},
]

revenue_by_channel = {}
for order in orders:
    channel = order["channel"] or "unknown"
    revenue_by_channel[channel] = revenue_by_channel.get(channel, 0) + order["total"]

for channel, revenue in revenue_by_channel.items():
    print(channel, revenue)

Two details carry this. revenue_by_channel.get(channel, 0) returns the running total so far, or 0 the first time a channel appears. And order["channel"] or "unknown" replaces None with a readable label, because None counts as false and or returns the right-hand side when the left is false.

This is a hand-written groupby. In Lesson 4.1 pandas does it in one line, and now you know what that line does underneath: walk the rows, find each row’s group, update that group’s total.

Comprehensions: building lists in one line

Very often a loop exists only to build a new list. A list comprehension says that directly:

Python
totals = [65.0, 18.0, 33.0, 35.0, 72.0, 58.0, 329.0]

with_rise = [round(t * 1.05, 2) for t in totals]
large = [t for t in totals if t >= 60]
print(with_rise)
print(large, len(large))

Read [t for t in totals if t >= 60] as “t, for each t in totals, if t is at least 60”. The part before for is what goes in; the if at the end filters. A dictionary comprehension works the same way with a key: value pair:

Python
countries = ["Canada", "united kingdom", "UNITED KINGDOM", "Saudi Arabia"]
clean = [c.strip().title() for c in countries]
print(clean)
print(sorted(set(clean)))            # unique values, sorted
print({c: len(c) for c in set(clean)})

A set keeps only unique values, so set(clean) collapses the two spellings of the UK into one once they are cleaned. You will do the same fix on a whole column with pandas in Lesson 3.2.

Loops versus pandas

In pandas you will almost never loop over rows. orders["total"].sum() does the summing loop, and orders.groupby("channel")["total"].sum() does the dictionary pattern above, both in fast compiled code. So why learn loops? Because they are how you check your understanding of what a pandas operation does, and because plenty of real work (looping over files, over months, over a list of countries to build a report) is still a loop around pandas code.

In the exercise you summarise twelve rows by country. Then you package logic like this into reusable functions and learn to read the errors Python shows you.

Key takeaways

  • A for loop runs its indented body once per item; create accumulators such as total = 0 before the loop, not inside it.
  • if / elif / else checks conditions top to bottom and runs only the first branch that is true.
  • counts[key] = counts.get(key, 0) + 1 is the standard pattern for counting or summing by group with a dictionary.
  • A list comprehension [expr for x in items if condition] builds a new list in one readable line.
  • In pandas you rarely write loops, because column operations do the looping for you; loops are still how you reason about what those operations do.

Further reading

Check your understanding

3 questions

Question 1 of 3

What does this print?

Python
totals = [65, 18, 33]
for t in totals:
    running = 0
    running += t
print(running)
Question 2 of 3

An order total of 150 goes through if t < 50: "small", elif t < 150: "medium", else: "large". Which label does it get?

Question 3 of 3

Which comprehension keeps only web orders’ totals from a list of order dictionaries?

Practice

Python exercise

Summarise twelve orders by country

orders holds twelve rows from the messy export, and the country names have inconsistent capitals. Build revenue_by_country, a dictionary from cleaned country name (use .strip().title()) to the sum of total. Build large_ids, a list of the order_ids whose total is 60 or more, in their original order. Set web_count to the number of orders whose channel is exactly "web".

orders = [
    {"order_id": 2574, "total": 65.0, "channel": "mobile app", "country": "Canada"},
    {"order_id": 2069, "total": 18.0, "channel": "mobile app", "country": "United States"},
    {"order_id": 1200, "total": 33.0, "channel": "mobile app", "country": "united kingdom"},
    {"order_id": 1809, "total": 35.0, "channel": "web", "country": "United States"},
    {"order_id": 1434, "total": 72.0, "channel": None, "country": "Saudi Arabia"},
    {"order_id": 2089, "total": 58.0, "channel": "marketplace", "country": "UNITED KINGDOM"},
    {"order_id": 2490, "total": 89.0, "channel": "web", "country": "United Arab Emirates"},
    {"order_id": 2985, "total": 24.0, "channel": "web", "country": "United Kingdom"},
    {"order_id": 2544, "total": 58.0, "channel": "mobile app", "country": "Jordan"},
    {"order_id": 2375, "total": 329.0, "channel": "mobile app", "country": "United Kingdom"},
    {"order_id": 1129, "total": 29.0, "channel": "web", "country": "United States"},
    {"order_id": 1176, "total": 44.0, "channel": "web", "country": "United States"},
]
# 1. Revenue per country, with country names cleaned by .strip().title()
revenue_by_country = {}

# 2. order_id of every order with a total of 60 or more, in the original order
large_ids = []

# 3. How many orders came through the "web" channel
web_count = 0
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

(no output)

Checks

  • Exactly six cleaned country names, with no case variants
  • United Kingdom adds up to 444.0
  • The country totals add up to 854.0, the sum of all twelve orders
  • large_ids is [2574, 1434, 2490, 2375]
  • web_count is 5