Skip to content

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