Skip to content

polars: Exercise 1 solution lacks the drop_nulls() guard that Exercise 2 has #589

Description

@mmcky

lectures/polars.md — Exercise 1's solution computes percentage change with first()/last() on columns that can contain nulls, without the drop_nulls() guard that Exercise 2's solution uses.

Exercise 2 (lines 762–764) guards:

*[pl.col(idx).drop_nulls().first().alias(f'{idx}_first') ...
*[pl.col(idx).drop_nulls().last().alias(f'{idx}_last') ...

Exercise 1 (line 688) does not:

((pl.col(tick).last() / pl.col(tick).first() - 1) * 100)

read_data_polars assembles the combined price table through a chain of join(..., how='full', coalesce=True) calls. Tickers with different trading calendars — different listing dates, market holidays — leave nulls at the head or tail of individual columns. pl.col(tick).first() then returns null, and the percentage change for that ticker becomes null rather than its actual value.

The sort('Date') added to read_data_polars in #408 fixes row ordering, and the comment at lines 671–672 documents why ("Hence the sort('Date') before returning, which any later first()/last() calculation relies on"). It does not address nulls — which is precisely why Exercise 2 needed a separate drop_nulls() guard. Exercise 1 has the same exposure and did not get the same fix.

CI does not catch this: nulls propagate into the bar chart silently rather than raising, so the build passes and the affected bar is simply wrong or absent.

Suggested fix — mirror Exercise 2:

((pl.col(tick).drop_nulls().last() / pl.col(tick).drop_nulls().first() - 1) * 100)

Found while validating the translation sync of #408 into the French, Farsi and Simplified Chinese editions. GitHub Copilot flagged it independently on all three translation PRs — QuantEcon/lecture-python-programming.fr#20, QuantEcon/lecture-python-programming.fa#144 and QuantEcon/lecture-python-programming.zh-cn#79 — which is what prompted the check here. Those PRs reproduce the source code byte-identically (verified: 52 code cells, non-comment content identical in all three), so correcting it here propagates to every edition on the next sync rather than needing three separate fixes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions