← Back to tsb playground

merge_ordered — Ordered Fill Merge

pandas equivalent: pd.merge_ordered(left, right, on="date")

mergeOrdered is an ordered merge (default outer join) that sorts the result by the key column(s). It is ideal for time-series and event data where both DataFrames have partially overlapping key ranges and you want a complete timeline with optional forward-fill (fill_method: "ffill") to carry values forward.

Key concepts

Basic outer ordered merge

import { DataFrame, mergeOrdered } from "tsb";

const left = DataFrame.fromColumns({
  date:  [1, 3, 5],
  price: [10, 30, 50],
});
const right = DataFrame.fromColumns({
  date:   [2, 3, 6],
  volume: [200, 300, 600],
});

const result = mergeOrdered(left, right, { on: "date" });
// date | price | volume
//    1 |    10 |   null
//    2 |  null |    200
//    3 |    30 |    300
//    5 |    50 |   null
//    6 |  null |    600

Forward-fill after merge

const result = mergeOrdered(left, right, {
  on: "date",
  fill_method: "ffill",
});
// date | price | volume
//    1 |    10 |   null   ← no earlier price to fill
//    2 |    10 |    200   ← price carried forward from date=1
//    3 |    30 |    300
//    5 |    50 |    300   ← volume carried forward from date=3
//    6 |    50 |    600

Inner join variant

// Only rows where both DataFrames have a key
mergeOrdered(left, right, { on: "date", how: "inner" });
// date | price | volume
//    3 |    30 |    300

Different key column names per side

const left2 = DataFrame.fromColumns({ t_left:  [1, 3, 5], a: [10, 30, 50] });
const right2 = DataFrame.fromColumns({ t_right: [2, 3, 6], b: [200, 300, 600] });

mergeOrdered(left2, right2, { left_on: "t_left", right_on: "t_right" });
// t_left | a    | b
//      1 |   10 | null
//      2 | null |  200
//      3 |   30 |  300
//      5 |   50 | null
//      6 | null |  600

Group-wise ordered merge (left_by / right_by)

// Perform the ordered merge independently for each group
const left3 = DataFrame.fromColumns({
  grp: ["A", "A", "B", "B"],
  k:   [1,   3,   1,   3],
  a:   [10,  30, 100, 300],
});
const right3 = DataFrame.fromColumns({
  grp: ["A", "A", "B", "B"],
  k:   [2,   3,   2,   3],
  b:   [20,  30, 200, 300],
});

mergeOrdered(left3, right3, {
  on: "k",
  left_by: "grp",
  right_by: "grp",
});
// grp | k | a    | b
//   A | 1 |   10 | null
//   A | 2 | null |   20
//   A | 3 |   30 |   30
//   B | 1 |  100 | null
//   B | 2 | null |  200
//   B | 3 |  300 |  300

Overlapping non-key columns — suffixes

const left4 = DataFrame.fromColumns({ k: [1, 2, 3], val: [10, 20, 30] });
const right4 = DataFrame.fromColumns({ k: [2, 3, 4], val: [200, 300, 400] });

mergeOrdered(left4, right4, { on: "k", suffixes: ["_L", "_R"] });
// k | val_L | val_R
// 1 |    10 |  null
// 2 |    20 |   200
// 3 |    30 |   300
// 4 |  null |   400

API reference

OptionTypeDefaultDescription
onstring | string[]Key column(s) present in both DataFrames
left_onstring | string[]Key column(s) in the left DataFrame
right_onstring | string[]Key column(s) in the right DataFrame
how"outer" | "inner" | "left" | "right""outer"Join type
fill_method"ffill" | nullnullForward-fill null gaps after merge
left_bystring | string[]Group columns in left DataFrame
right_bystring | string[]Group columns in right DataFrame
suffixes[string, string]["_x", "_y"]Suffixes for overlapping non-key columns