Generation¶
Every function here takes a TableSpec as its first argument, and every one
has a FrameSpec classmethod that forwards to it with cls.spec -- see
Generating data for what the options mean and
Specs as values for when to reach for which.
generate¶
generate
¶
generate(spec: TableSpec, n: int, *, method: Method = 'random', seed: int | None = None, references: References = None, cycles: int = 0, self_references: int = 0, max_bytes: int | None = None) -> DataFrame
Generates a DataFrame matching spec.
method="random" (default): n rows, each column drawn independently.
method="cartesian": guarantees a minimum level of coverage. Builds the
cartesian product of every Enum/Boolean column's full set of values,
crossed with the negative/zero/positive/null partitions of every bounded
numeric column, so every enum combination appears alongside every numeric
sign/null case. n is then a minimum: if that coverage set has fewer
than n rows it is padded with random rows; if it has more, all of it is
kept.
ColSpec.rules and any ForeignKey the spec declares are then applied as
vectorised passes over the generated frame, regardless of method. Each
pass sees the frame the passes before it produced, and they run in the
order their reads and writes imply -- a rule keyed on a foreign-keyed
column reads the parent's values, not the freely generated ones they
replaced -- so the result satisfies the same declarations validate
checks it against.
A foreign key is only made referentially consistent where data for its
target is available: self-referencing keys always are, sampled from this
same frame; a key referencing another spec only is if references
carries an entry for it, keyed by the spec, its class, or its name --
otherwise that column is left exactly as freely generated. Composite keys
are sampled as one joint pick per row; a single-column key whose ColSpec
is unique=True samples without replacement when the parent has enough
distinct rows to cover n.
A unique=True column is drawn without replacement by the engine itself,
and a __unique_together__ group is separated afterwards by resampling
the rows that repeat a combination. Either refuses, naming the column or
the group, when the domain is too small to cover n.
A spec declaring a Hierarchy has its two link columns rewritten as a
forest of the declared depth. cycles and self_references then damage it
on purpose -- closing that many chains into loops, and pointing that many
rows at themselves -- which is how a graph walk gets something to fail
against. Both default to zero, and validate() reports whatever they
injected.
Before anything is allocated the frame's size is estimated from the
declaration. Past four gibibytes that is a warning naming the estimate;
max_bytes= makes it a refusal instead, and max_bytes=0 silences both.
The whole frame is built before this returns. scan() is the lazy
verb: it generates as the plan is collected, so only the columns and
rows a plan asks for are made.
generate_batches¶
generate_batches
¶
generate_batches(spec: TableSpec, n: int, *, batch_size: int = 100000, method: Method = 'random', seed: int | None = None, references: References = None) -> Iterator[DataFrame]
Yields chunks of generated rows without holding all n in memory.
Each batch is a window onto the one frame seed describes: a column no
pass rewrites holds, batch by batch, exactly the rows generate(n, seed=seed)
would, whatever batch_size is. What is drawn per batch instead --
deterministic, but not row for row the whole frame's -- is a column with
rules, a foreign key, a composite key, and a List column's elements.
Uniqueness only holds within a batch, not across the whole n: that
applies to a unique=True column, a __unique_together__ group, and a
foreign-key column sampled without replacement alike.
scan¶
scan
¶
A LazyFrame that generates rows on demand.
generate() builds the whole frame before it returns. scan() hands back
a frame that has not been built: polars asks for the columns and the rows
it actually needs, and only those are generated. So
Orders.scan(50_000_000, seed=1).sink_parquet("orders.parquet")
streams in bounded memory, and
Orders.scan(50_000_000, seed=1).select("total").head(5).collect()
generates five rows of one column.
Projecting cannot change what a column holds. Every column is seeded by its
name and every pass by what it is for, so dropping a column's neighbours
leaves it alone: scan(n, seed=s).select(cols).collect() is
scan(n, seed=s).collect().select(cols), for every subset. That is what
makes the pushdown free rather than a trade.
Rows come in batches, so a scan carries generate_batches' terms: a
Hierarchy is refused, uniqueness holds within a batch rather than across
n, and a column no pass rewrites holds the rows generate(n, seed=s)
would while a ruled or foreign-keyed one is drawn per batch.
needed_columns
¶
needed_columns(spec: TableSpec, wanted: frozenset[str]) -> list[str]
wanted plus every column its values depend on, in spec order.
A projection cannot simply drop the rest: a rules column's values depend
on the columns its when reads, a composite key's on every member of the
group, a foreign key's on the other columns of its key. passes_of
already knows what each pass reads and writes, so the closure is that
relation followed to a fixed point -- generate these, hand back only what
was asked for.
scan
¶
scan(spec: TableSpec, n: int, *, seed: int | None = None, batch_size: int | None = None, method: Method = 'random', references: References = None) -> LazyFrame
A LazyFrame of n generated rows, produced as they are collected.
batch_size left unset lets polars ask for the size it would like, so a
sink gets the batches it writes best; setting it pins the size whatever
polars asks.
estimated_size¶
estimated_size
¶
estimated_size(spec: TableSpec, n: int) -> int
Bytes generate(spec, n) is expected to hold, as whole bytes.
See the module docstring for what this does and does not count.
sink_parquet¶
sink_parquet
¶
sink_parquet(spec: TableSpec, path: str | Path, n: int, *, batch_size: int = 100000, compression: ParquetCompression = 'zstd', method: Method = 'random', seed: int | None = None, references: References = None, **kwargs: Any) -> None
Generates n rows and streams them to a Parquet file in batches.
Extra keyword arguments go to pl.LazyFrame.sink_parquet.
sink_ipc¶
sink_ipc
¶
sink_ipc(spec: TableSpec, path: str | Path, n: int, *, batch_size: int = 100000, compression: IpcCompression | None = 'zstd', method: Method = 'random', seed: int | None = None, references: References = None, **kwargs: Any) -> None
Generates n rows and streams them to an Arrow IPC / Feather file in batches.
Extra keyword arguments go to pl.LazyFrame.sink_ipc.
sink_csv¶
sink_csv
¶
sink_csv(spec: TableSpec, path: str | Path, n: int, *, batch_size: int = 100000, include_header: bool = True, method: Method = 'random', seed: int | None = None, references: References = None, **kwargs: Any) -> None
Generates n rows and streams them to a CSV file in batches.
Extra keyword arguments go to pl.LazyFrame.sink_csv.
sink_ndjson¶
sink_ndjson
¶
sink_ndjson(spec: TableSpec, path: str | Path, n: int, *, batch_size: int = 100000, method: Method = 'random', seed: int | None = None, references: References = None, **kwargs: Any) -> None
Generates n rows and streams them to a newline-delimited JSON file in batches.
Extra keyword arguments go to pl.LazyFrame.sink_ndjson.