Read & explore¶
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'}
laterite.read hands back a group as a born-typed polars frame. Index it
by 4-letter code (ags["LOCA"]) and you get a real DataFrame — already typed.
The dtype row is the AGS TYPE row: 2DP lands as Float64, ID as
String. No .cast(...), no pd.to_numeric, no per-column cleanup. The
arithmetic just works because the column was numeric the moment it was read.
Three doors in¶
read takes whichever form your data already has — there's no separate
loader to pick:
laterite.read("examples/sample_site.ags") # a path
laterite.read(text="GROUP,...\n...") # an in-memory AGS4 string
laterite.read(data=raw_bytes) # raw bytes (an upload, a download)
All three return the same object, so the rest of your code doesn't care where the file came from.
Tip
Typing happens at read time straight from the dictionary, so a group that
isn't in your file simply isn't a key. Iterate ags to see what's present.
Curious how the dtype gets fixed from the AGS TYPE row? See how this works → born-typed.
Next → Validate in Python