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.
how: "outer" — keeps all keys from both DataFrames.fill_method: "ffill" forward-fills null gaps in non-key columns after the merge.left_by / right_by: perform the ordered merge independently per group and concatenate.left_on / right_on: use different key column names per side.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
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
// Only rows where both DataFrames have a key
mergeOrdered(left, right, { on: "date", how: "inner" });
// date | price | volume
// 3 | 30 | 300
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
// 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
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
| Option | Type | Default | Description |
|---|---|---|---|
on | string | string[] | — | Key column(s) present in both DataFrames |
left_on | string | string[] | — | Key column(s) in the left DataFrame |
right_on | string | string[] | — | Key column(s) in the right DataFrame |
how | "outer" | "inner" | "left" | "right" | "outer" | Join type |
fill_method | "ffill" | null | null | Forward-fill null gaps after merge |
left_by | string | string[] | — | Group columns in left DataFrame |
right_by | string | string[] | — | Group columns in right DataFrame |
suffixes | [string, string] | ["_x", "_y"] | Suffixes for overlapping non-key columns |