Get one group as a typed frame¶
Available in: Python · Node · DuckDB · Browser
Pull a single AGS group straight into a dataframe whose dtypes already match
the group's TYPE row — no casting at the call site.
import laterite
# Read an AGS4 file. `read` takes a path, or text=… / data=… (the three doors).
ags = laterite.read("examples/sample_site.ags")
# A group comes back as a born-typed polars frame — the dtype *is* the TYPE row.
loca = ags["LOCA"]
print(loca.select("LOCA_ID", "LOCA_NATE", "LOCA_GL").head(2))
print({h: str(loca[h].dtype) for h in ("LOCA_ID", "LOCA_NATE", "LOCA_GL")})
assert str(loca["LOCA_GL"].dtype) == "Float64" # 2DP → Float64 (no manual cast)
assert str(loca["LOCA_NATE"].dtype) == "Float64" # 2DP → Float64
assert str(loca["LOCA_ID"].dtype) == "String" # ID → String
shape: (2, 3)
┌─────────┬───────────┬─────────┐
│ LOCA_ID ┆ LOCA_NATE ┆ LOCA_GL │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 │
╞═════════╪═══════════╪═════════╡
│ BH01 ┆ 451105.75 ┆ 23.68 │
│ BH02 ┆ 451235.21 ┆ 32.49 │
└─────────┴───────────┴─────────┘
{'LOCA_ID': 'String', 'LOCA_NATE': 'Float64', 'LOCA_GL': 'Float64'}
ags["LOCA"] is a born-typed polars DataFrame: the dtype is the TYPE
row. The 2DP columns (LOCA_NATE, LOCA_GL) arrive as Float64; the
ID column (LOCA_ID) stays String. You can sort, add, and join
immediately — no .cast(), no pd.to_numeric.
ags.table("LOCA") is the same call by another name — use whichever reads
better at the call site.
The three input doors — read takes a path, or read from memory:
laterite.read("delivery.ags") # a path
laterite.read(text=ags_string) # text already in hand
laterite.read(data=ags_bytes) # raw bytes (e.g. an upload)
A missing group raises KeyError; check "LOCA" in ags first if the group
may be absent.
import { read } from "laterite";
import assert from "node:assert/strict";
// Read an AGS4 file. `read` takes a path, raw bytes, or { text } (the three doors).
const ags = read("examples/sample_site.ags");
// A group comes back as a born-typed arrow-js Table — the dtype *is* the TYPE row.
const loca = ags.table("LOCA");
const dtype = (name) =>
String(loca.schema.fields.find((f) => f.name === name).type);
console.log(
`LOCA_ID[0]=${loca.getChild("LOCA_ID").get(0)} LOCA_GL[0]=${loca.getChild("LOCA_GL").get(0)}`,
);
console.log({
LOCA_ID: dtype("LOCA_ID"),
LOCA_NATE: dtype("LOCA_NATE"),
LOCA_GL: dtype("LOCA_GL"),
});
assert.equal(dtype("LOCA_GL"), "Float64"); // 2DP → Float64 (no manual cast)
assert.equal(dtype("LOCA_NATE"), "Float64"); // 2DP → Float64
assert.equal(dtype("LOCA_ID"), "Utf8"); // ID → Utf8
assert.equal(typeof loca.getChild("LOCA_GL").get(0), "number"); // real JS numbers
file.table("LOCA") is a born-typed arrow-js Table — the Node
counterpart of the polars frame, from the same Arrow columns the engine
builds for every surface. 2DP columns arrive as Float64 (real JS
numbers from .get()), ID stays Utf8. The three input doors mirror
Python: read(path), read(bytes), or read(undefined, { text }).
-- One group as a table — columns come back born-typed (the dtype IS the TYPE row).
-- expect-rows: 2
SELECT loca_id, loca_nate, loca_gl
FROM read_ags('examples/sample_site.ags', 'LOCA')
ORDER BY loca_id LIMIT 2;
read_ags(path, group) exposes the group as a table function with the
same born-typed columns — 2DP is DOUBLE, ID is VARCHAR — so
numeric predicates (WHERE loca_gl < 0) mean what they say with no
CAST. Feed it straight into CREATE TABLE … AS to materialise, or join
it against other groups (see SQL across groups).
In the web app, the Explore pane shows every group as the same born-typed table — sortable columns, the KEY chain to parent/child groups, and charts over the numeric columns — powered by the same Arrow columns via wasm, entirely client-side.
See also: Read · Born-typed reads