Skip to content

finprint.tracked

module finprint

Proxies that record what is done to a frame: TrackedFrame, TrackedSeries and their helpers.

A TrackedFrame wraps a DataFrame and forwards everything to it. Public methods that return a DataFrame are recorded as verb steps and their results are tracked; methods that return a Series give a TrackedSeries that remembers how it was built (an expression over frame versions), so df["net"] = df["spend"] * 0.85 can be replayed. groupby, rolling, resample, expanding and ewm return a deferred object whose next aggregation is recorded as one step. In-place edits (__setitem__, .loc/.iloc setters, inplace=True, setting columns) are recorded as verbs that give the frame a new version id.

pandas is never patched: code that never touches a proxy behaves exactly as before. pandas functions do not accept a proxy: pd.merge(a, b), pd.concat([a, b]), pd.to_datetime(s) and pd.DataFrame(a) raise a TypeError that names the recorded alternative (the method form a.merge(b), rec.concat / rec.merge / rec.call), because their result would come back untracked and the step would be lost. Operators mixing a plain pandas object with a proxy (plain * tracked) defer to the proxy (__pandas_priority__) and are recorded. ur.untrack(frame) gives the real object for libraries that need one.

Edits made through a tracked series (s[mask] = v, s.loc[k] = v, s.clip(..., inplace=True), s.name = ...) become part of the series’ expression, so a column assigned from it later replays with them.

A value computed from tracked data (s.median(), s.quantile(0.9), s.max(), s.iloc[0], df.at[k, c], a number, date, duration or text; never a yes/no answer such as s.any()) is a TrackedScalar: it carries its expression, so a step using it records the expression ({"__scalar__": ...}) and apply recomputes it on new data. Arithmetic, comparisons, round/abs/math.floor, NumPy ufuncs and its own methods (ts.normalize()) stay tracked. Taking the plain value out (float, int, str/format, bool in an if, np.asarray, math.log, fp.untrack) tells the session, which marks the next step using that literal (or, for bool, the next step recorded) so apply refuses it.

class

class TrackedFrame(TrackedBase)

A DataFrame proxy bound to a session. Every frame version has an id (in1, f7…).

Methods and properties (2)

init constructor

TrackedFrame(
self,
df: pd.DataFrame,
session: Session,
fid: str,
state: str | None = None,
) -> None

frame_id property

frame_id: str

The id of this frame’s current version (as steps in the recipe name it).

Source: packages/finprint/src/finprint/tracked.py, line 91

class

class TrackedSeries(TrackedBase)

A Series proxy that remembers its expression over frame versions (never recorded as a step by itself).

Methods and properties (1)

init constructor

TrackedSeries(
self,
s: pd.Series,
session: Session,
expr: Any,
refs: list[str],
replayable: bool,
) -> None

Source: packages/finprint/src/finprint/tracked.py, line 317

class

class TrackedAccessor

series.str / .dt / .cat on a tracked series.

Methods and properties (1)

init constructor

TrackedAccessor(
self,
sess: Session,
node: Any,
refs: list[str],
rep: bool,
acc: Any,
) -> None

Source: packages/finprint/src/finprint/tracked.py, line 500

class

class TrackedIndexer

frame.loc / .iloc / .at / .iat: reads are verbs, writes are in-place verbs.

Methods and properties (1)

init constructor

TrackedIndexer(self, tf: TrackedFrame, kind: str) -> None

Source: packages/finprint/src/finprint/tracked.py, line 534

class

class TrackedDeferred

A tracked groupby (or rolling, resample, expanding, ewm): the next aggregation is recorded as one step.

Methods and properties (1)

init constructor

TrackedDeferred(
self,
sess: Session,
verb: str,
self_node: Any,
refs: list[str],
call: dict[str, Any],
select: Any,
obj: Any,
rep: bool,
reasons: list[str],
) -> None

Source: packages/finprint/src/finprint/tracked.py, line 569

class

class TrackedScalar(TrackedBase)

A value computed from tracked data (s.median(), s.max() - pd.Timedelta(weeks=52), s.iloc[0]).

It behaves like its value in pandas calls and arithmetic, and a step that uses it records its expression ({"__scalar__": ...}), so verify recomputes the same value from the same data and apply recomputes it on new data. The plain value comes out through float, int, str/format, bool, np.asarray or fp.untrack; the session is told, and a step that then uses the literal is refused by apply (DATA_DERIVED_LITERAL). repr shows the value and tells nobody.

Methods and properties (1)

init constructor

TrackedScalar(
self,
v: Any,
session: Session,
expr: Any,
refs: list[str],
replayable: bool,
) -> None

Source: packages/finprint/src/finprint/tracked.py, line 657

function

def renders_text(node: Any) -> bool

A to_* call (to_csv(), to_json(), to_string()): its text is output for the analyst, not a value a later step computes with, so it stays a plain str.

Source: packages/finprint/src/finprint/tracked.py, line 77

function

def traceable(v: Any, *, allow_bool: bool = False) -> bool

A result worth tracking as a computed value: a number, date, duration or text (a bool only from scalar maths).

Source: packages/finprint/src/finprint/tracked.py, line 84

NameTypeValueDescription
DEFERRED{'groupby', 'rolling', 'expanding', 'ewm', 'resample'}
INDEXERS{'loc', 'iloc', 'at', 'iat'}
SERIES_ACCESSORS{'str', 'dt', 'cat'}
PANDAS_FUNCTION_MESSAGE'pandas functions do not accept a tracked {what}: the result would come back ...