Skip to content
  • Overview
  • Curriculum
    • FLUMental maths and numerical fluency
    • MKTMarkets and products
    • CSData structures and algorithms
    • PYPython and data for quants
      • 1Python fundamentals

        • Idiomatic Python: generators, itertools and the gotchas
      • 2NumPy

        • NumPy: broadcasting, axes and vectorisation
      • 3pandas

        • pandas: groupby, joins, resampling and time zones
      • 4Market data handling

        • Market data in pandas, and where look-ahead hides
      • 5Vectorised backtesting

        • Vectorised backtesting: signal to position to P&L
      • 6Performance

        • Performance: profiling, vectorising and when to leave Python
    • NUMNumerical methods
    • SYSSystems and low latency

Practise

  • Question bank
  • Mental arithmetic
  • Market simulator
  • Arbitrage trees
  • Horse racing
  • Bid book
  • Screening tests
  • Mock papers

Reference

  • Formula reference
  • Search

Your record

  • Review queue
  • Progress
  • Leaderboard
  • Profile
  • Invite friends
AccountSend feedback
  1. Curriculum
  2. /Quantitative development
  3. /Python and data for quants
  4. /pandas

pandas: groupby, joins, resampling and time zones

PY · Chapter 3·12 min read·Asked at Two Sigma, Citadel Securities, Point72, QuantCo

Assumes NumPy: broadcasting, axes and vectorisation.

After this lesson you should be able to

  • Use groupby–transform to work within groups without losing the shape.
  • Choose the right join, including merge_asof.
  • Handle time zones and resampling without silently shifting the data.

pandas is where research code actually lives, and the operations that matter are the ones that respect grouping and time. Nearly every subtle bug in a research pipeline is a join that dropped rows, a groupby that reduced when it should have transformed, or a timestamp in the wrong zone.

VerbReturnsUse for
aggOne row per groupSummaries — mean return by sector
transformThe original shapeWithin-group standardisation
applyWhatever you returnFlexible and slow; a last resort
filterA subset of rowsKeeping groups meeting a condition
Table 3.1 · The groupby verbs. transform is the one that matters most in signal work: cross-sectional z-scoring is df.groupby("date")["signal"].transform(lambda s: (s - s.mean()) / s.std()), and it returns something you can assign straight back.
Sort–merge, n log n1,660,964Nested loop, n²10,000,000,000
Figure 3.2 · Joining a hundred thousand rows. Both give the same answer. merge and merge_asof sort and sweep; a loop that looks up each row in a DataFrame by value does the other one, which is why a join written as a loop appears to hang rather than to be slow.
JoinKeepsWatch for
innerOnly matching keysSilent row loss
leftAll of the leftNaNs where the right had no match
outerEverythingRow explosion on duplicate keys
merge_asofNearest earlier keyBoth sides must be sorted
Table 3.3 · Joins. Always check the row count before and after a merge. A join on a key with duplicates multiplies rows rather than matching them, and it is the failure that most often goes unnoticed until a result looks too good.

Why merge_asof is the market-data join. Market data arrives on irregular timestamps, so an exact-match join finds almost nothing and a nearest-match join can reach forward in time. merge_asof with the default backward direction matches each row to the most recent row on the other side at or before it — which is precisely "what did I know at this instant". The tolerance argument then caps how stale a match may be, so a quote from an hour ago does not get attached to a trade. It is the one join whose semantics are built for causality rather than for set membership.

Example 3.4

You join a daily signal onto minute-bar prices and the row count explodes from 100,000 to 39 million. What happened?

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    rowsout=∑knkleft×nkright\text{rows}_{\text{out}} = \sum_k n_k^{\text{left}} \times n_k^{\text{right}}rowsout​=k∑​nkleft​×nkright​
  2. Substitute
    joining on date alone, with 390 minutes per date\text{joining on date alone, with } 390 \text{ minutes per date}joining on date alone, with 390 minutes per date
  3. Solve
    100,000×390=39,000,000100{,}000 \times 390 = 39{,}000{,}000100,000×390=39,000,000
  4. Answer
    a many-to-many join on a non-unique key\text{a many-to-many join on a non-unique key}a many-to-many join on a non-unique key

Sanity check. That may even be what you wanted — broadcasting a daily signal onto every minute is a legitimate operation. What is not acceptable is being surprised by it, which is why checking the row count against the expected multiple is a standard step.

The rest of this lesson is in Premium

You have read the opening. 10 more sections follow, including 3 worked examples and 3 quick checks.

Start the free 7-day trialSign in

Nothing is charged for 7 days, and you can cancel before then. Or read Complexity: reading it off, and deriving it in full, free.

← NumPy: broadcasting, axes and vectorisationMarket data in pandas, and where look-ahead hides →
On this page
  • The groupby verbs
  • Joining a hundred thousand rows
  • Joins
  • Worked example

QuantMax · 141 lessons · 1342 questions · c5c0caa

  • Premium
  • Arbitrage trees
  • Horse racing
  • Invite friends
  • Account
  • About QuantMax
  • Terms
  • Privacy

Firm names identify publicly reported question patterns and nothing more. QuantMax is not affiliated with, endorsed by, or recruiting for any firm named in the curriculum. Everything you do in lessons and the question bank is kept to your account.