Create your own
Lesson illustration

Aggregating Observations with a For-Loop

Welcome back. In the previous lesson, you used if, elif, and else to choose an action for one daily observation. Quantitative research rarely stops at one day: a notebook may need to total volume over hundreds of dates, count decline days, or build a running value from an entire return history.

A for loop lets Python visit each item in an ordered sequence and apply the same logic to it. In this lesson, you will use a loop together with an accumulator: a variable that keeps an updated result as observations are processed.


From one observation to a sequence

Suppose you have daily returns stored in a list:

daily_returns = [0.010, -0.020, 0.015]

You could handle each value separately:

print(daily_returns[0])
print(daily_returns[1])
print(daily_returns[2])

But this approach is fragile. If the list gains another day, your code does not automatically include it. If the list is shorter than expected, indexing can fail. For real market datasets with many thousands of observations, writing one line per date is impossible.

A for loop expresses the intended rule more clearly:

for daily_return in daily_returns:
    print(daily_return)

Read this as:

“For each daily_return in the sequence called daily_returns, run the indented code.”

Python assigns the first list item to daily_return, runs the indented body, then assigns the next item and runs the body again. It stops when there are no items left.

Python For Loops - Python Tutorial for Absolute Beginners

Watch “Python For Loops - Python Tutorial for Absolute Beginners” from Programming with Mosh for a compact visual introduction to the structure of a for loop.

Watch the loop basics. Focus on the colon after the loop header, the indentation of the loop body, and how the loop variable takes a new value on each pass.

Like an if statement, a for statement ends with a colon and its body must be indented by four spaces:

for daily_return in daily_returns:
    print(daily_return)

print("All observations processed.")

The final print is not indented, so it runs only once, after the loop finishes.

The diagram traces `for num in odds:` through the list `[1, 3, 5]`: Python assigns each list value to `num`, runs `print(num)`, and finishes only after every value has been processed.

Choose a meaningful loop-variable name. daily_return, day, price, and volume communicate what one item represents. A name such as x or thing makes research code harder to inspect later.


Aggregation: preserving a result while the loop runs

Printing each observation is useful for understanding loops, but research often requires one combined answer. Examples include:

  • total reported trading volume;
  • number of negative-return days;
  • total amount invested;
  • a sequence of running totals.

An accumulator is the variable that stores this evolving result. The standard pattern has three parts:

  1. Initialize the accumulator before the loop.
  2. Update it once per observation inside the loop.
  3. Use or display the completed result after the loop.

Foundations of Data Science / Python - Loops / Hands-on

Read the “Accumulation” section. It introduces the accumulator pattern and uses a running total to show why the starting value and the location of the update both matter.

In the “Accumulation” subsection, begin with the paragraph explaining the idea of accumulation. Read the accumulator pattern, then study the following example that sums integers. Pay particular attention to the placement of total = 0 before the loop and the update within the indented block.

A running sum of observations

Here is a loop that adds each daily return to a running total:

daily_returns = [0.010, -0.020, 0.015]

total_return = 0

for daily_return in daily_returns:
    total_return = total_return + daily_return

print(total_return)

Output:

0.004999999999999999

The display is a normal floating-point representation issue; mathematically, the result is , or .

Trace the accumulator carefully:

Loop passCurrent daily_returnPrevious total_returnUpdated total_return
Before loop0
10.01000.010
2-0.0200.010-0.010
30.015-0.0100.005

The important line is:

total_return = total_return + daily_return

Python first evaluates the expression on the right, using the current total and the current observation. It then stores that new result in total_return.

Python provides a shorter equivalent form:

total_return += daily_return

For now, read += as “update this variable by adding the value on the right.” The same loop can therefore be written:

daily_returns = [0.010, -0.020, 0.015]

total_return = 0

for daily_return in daily_returns:
    total_return += daily_return

print(total_return)

This is an arithmetic sum of simple returns, not the correct compounded investment return over multiple days. Treat it as a loop and aggregation example only. In the financial-returns module, you will calculate compounded wealth properly; confusing the two would produce an incorrect backtest result.


Why initialization and indentation matter

The initial value must match the aggregation you intend to perform.

For a sum, begin at zero:

total_volume = 0

For a count, also begin at zero:

negative_days = 0

Do not reset the accumulator inside the loop:

# Incorrect: total_volume is reset on every pass.
for volume in volumes:
    total_volume = 0
    total_volume += volume

After each iteration, this code throws away the earlier result. The final value is just the last volume, not the total.

The correct version initializes once, before Python begins visiting the sequence:

volumes = [1200000, 950000, 1600000]

total_volume = 0

for volume in volumes:
    total_volume += volume

print(total_volume)

Output:

3750000

Similarly, the final print normally belongs outside the loop:

for volume in volumes:
    total_volume += volume

print(total_volume)

If you indent it, Python prints a running total after every observation instead:

for volume in volumes:
    total_volume += volume
    print(total_volume)

That can be helpful while checking code, but it is a different task.


Counting observations that meet a rule

A loop can use the conditional logic from the previous lesson. For example, suppose you want to count negative-return days.

daily_returns = [0.010, -0.020, 0.015, -0.004, 0.000]

negative_days = 0

for daily_return in daily_returns:
    if daily_return < 0:
        negative_days += 1

print(negative_days)

Output:

2

There are now two nested structures:

for daily_return in daily_returns:
    if daily_return < 0:
        negative_days += 1

The for loop processes every return. On each pass, the if statement decides whether that return should increase the count.

Notice the indentation levels:

  • for starts at the left margin.
  • if is indented once because it belongs to the loop.
  • negative_days += 1 is indented twice because it belongs to the conditional block.

A zero return does not meet the rule daily_return < 0; it is not counted as a decline. If your research definition instead required “non-positive days,” you would write daily_return <= 0. As before, the boundary rule is part of the specification.


Notebook lab: aggregate market observations

You previously worked with a list of dictionaries, where each dictionary represented one daily record. Now aggregate two results across that list: total reported volume and the number of decline days.

Create a Markdown cell:

## Daily-observation aggregation

This cell sums reported volume and counts days with a negative simple return.

Then run this code cell:

price_days = [
    {"date": "2024-01-02", "daily_return": 0.010, "volume": 1200000},
    {"date": "2024-01-03", "daily_return": -0.020, "volume": 950000},
    {"date": "2024-01-04", "daily_return": 0.015, "volume": 1600000},
]

total_volume = 0
decline_days = 0

for day in price_days:
    total_volume += day["volume"]

    if day["daily_return"] < 0:
        decline_days += 1

print(f"Total reported volume: {total_volume}")
print(f"Number of decline days: {decline_days}")

Expected output:

Total reported volume: 3750000
Number of decline days: 1

This code contains two accumulators:

AccumulatorStarting valueUpdate ruleMeaning after the loop
total_volume0Add each day["volume"]Sum of volume across all records
decline_days0Add 1 only when return is negativeCount of records meeting the decline rule

To see the program state while it runs, temporarily add this line as the final line inside the loop:

    print(day["date"], total_volume, decline_days)

This is a basic debugging technique: observe the accumulator after each record rather than trusting the final result blindly. Once the intermediate values make sense, remove the debugging line to keep the notebook output clean.

Finally, add a fourth observation to price_days, predict how the two final values should change, and rerun the entire cell. A loop should include the new observation without any change to the loop itself. That scalability is the reason loops are central to data analysis.


Common mistakes to recognize early

These mistakes are especially common in first research notebooks:

MistakeWhat happensCorrection
Initializing total_volume inside the loopEarlier observations are discardedInitialize once before for
Forgetting to update the accumulatorFinal result remains 0Put += in the loop body
Printing inside the loop unintentionallyYou get one output per observationMove the final print left to the margin
Using = instead of +=The old total is overwrittenUse total += value for accumulation
Writing if at the left marginThe condition runs only after the loop, using the final itemIndent if so it runs for every item
Trying to add text to a numberPython cannot form a numeric sumEnsure every value follows a stated data policy before aggregation

A loop assumes the sequence is usable. In later modules, you will inspect real price data for missing values, duplicated dates, and nonnumeric fields before relying on totals, counts, or derived returns.


Key takeaways

A for loop repeats an indented block once for each item in a sequence.

  • The loop variable represents the current observation, such as day or daily_return.
  • An accumulator stores a result that is updated during each pass.
  • Initialize sum and count accumulators to 0 before the loop.
  • Use += to update a running total or count.
  • Put a conditional inside a loop when every observation must be checked against a rule.
  • Keep final reporting outside the loop unless you intentionally want intermediate output.
  • A sum of simple returns is not a compounded investment return; defining the quantity correctly matters as much as writing the code.

Next, you will define reusable Python functions. This will let you package a repeated calculation, such as counting decline days, into a named operation with inputs and a returned result.

Can't find a good explanation? Sign up and we'll make it for you

Sign up