README

ihb2032/MoonFrame/series does not have a README file

#
Series

pub struct Series {
// private fields
} derive(Eq,
Debug
)

A one-dimensional, dtype-aware column with a name. Series is the per-column unit behind DataFrame, exposing the structural, nullability, transform, cast, and (in series_stats.mbt) statistics surface that the frame and io layers build on.

Its backend is an internal representation — Arrow-style storage with an explicit validity bitmap, or an all-valid unboxed numeric fast path — decided by a column's content rather than by its caller: the row rebuilds and the expression engine's computed columns converge a null-free numeric result onto the fast path (try_column_to_numeric), the nullable constructors and cast produce a Builtin column whatever they started from, and the backend-preserving windows leave a column on whichever backend they found it. The supported API neither observes nor selects it: the few methods that do (storage / storage_kind / is_canonical) are engine seams, absent from the generated interface and carrying no compatibility promise, so a caller outside this module has no supported way to tell which one a column is on — and no way at all to choose it, since nothing here moves a column onto a backend its content does not call for. See internal/column and docs/performance.md.

The fields are priv, so the struct is opaque outside this package: reach a column through the value-level accessors (name() / dtype() / len() / get() / to_scalars() / …) and build one through the named constructors, so the underlying column always keeps its data-and-validity invariants.

#
Series::cast

Cast to the target dtype, the single cross-dtype conversion entry (Polars' Series.cast). Delegates to BuiltinColumn::cast; the supported targets are:

  • Int — identity on Int; Float truncates toward zero (NaN, ±Inf, and out-of-Int64-range values raise ParseError); Bool → 1 / 0; String parses plain base-10 integers (other forms raise ParseError).
  • Float — Int promoted; identity on Float; Bool → 1.0 / 0.0; String parses decimals / scientific notation.
  • String — every dtype renders to its value form; never rejects a value, so the only failure is the unsupported-target guard below.

Null slots are preserved verbatim. A numeric result lands on the Builtin backend; the engine re-converges it onto the unboxed fast path where a later step benefits. Bool and Null targets raise Unsupported(_).

#
Series::count

fn Series::count(self : Series) -> Int

Count of non-null cells.

#
Series::drop_nulls

fn Series::drop_nulls(self : Series) -> Series

Drop every null cell. The returned series has len = original.len -original.null_count and no null cells left. A source that already has no nulls is returned unchanged — so an all-valid Numeric column stays on the fast path (no bitmap to carry); otherwise the kept cells are gathered, and a numeric result — now necessarily all-valid — canonicalises onto Numeric (gather_series), so dropping nulls cannot leave a numeric column off the fast path.

#
Series::dtype

Logical dtype. Delegates to the underlying column so the two never drift apart.

#
Series::equal

fn Series::equal(Series, Series) -> Bool

#
Series::fill_null

Replace every null cell with value. raise TypeMismatch(...) if value is Scalar::Null (filling nulls with null is meaningless) or if its dtype differs from the series'. The returned series has a fully-valid bitmap and keeps the source storage backend (preserve_backend) — a no-op fill on an all-valid Numeric column stays Numeric.

#
Series::first

First cell as a Scalar, in row order — positional, so it skips nothing (a present NaN is returned verbatim). Total (Polars' Series.first): an empty series, or a leading null cell, yields Scalar::Null.

#
Series::from_bool_options

fn Series::from_bool_options(name : String, values : Array[Bool?]) -> Series

Build a Bool series where None entries are nulls.

#
Series::from_bools

fn Series::from_bools(name : String, values : Array[Bool]) -> Series

Build a Bool series from raw values (no nulls). Bool is non-numeric, so it lands on the Builtin backend. values is defensively copied, so the series is independent of the caller's array.

#
Series::from_float_options

fn Series::from_float_options(name : String, values : Array[Double?]) -> Series

Build a Float series where None entries are nulls. Nullable, so it lands on the Builtin backend.

#
Series::from_floats

fn Series::from_floats(name : String, values : Array[Double]) -> Series

Build a Float series from raw values (no nulls). Lands on the Numeric fast-path backend. Like from_ints, values is defensively copied, so the series is independent of the caller's array.

#
Series::from_int_options

fn Series::from_int_options(name : String, values : Array[Int64?]) -> Series

Build an Int series where None entries are nulls. Nullable, so it lands on the general-purpose Builtin backend.

#
Series::from_ints

fn Series::from_ints(name : String, values : Array[Int64]) -> Series

Build an Int series from raw values (no nulls). Lands on the Numeric fast-path backend — no validity bitmap is allocated.

values is defensively copied, so a Series is independent of the caller's array: mutating values afterwards does not change the series' cells. (The nullable constructors from_*_options likewise copy while boxing into Option.)

#
Series::from_string_options

fn Series::from_string_options(name : String, values : Array[String?]) -> Series

Build a String series where None entries are nulls.

#
Series::from_strings

fn Series::from_strings(name : String, values : Array[String]) -> Series

Build a String series from raw values (no nulls). String is non-numeric, so it lands on the Builtin backend. values is defensively copied, so the series is independent of the caller's array.

#
Series::gather

fn Series::gather(self : Series, indices : Array[Int]) -> Series raise
DataError

Gather: produce a new series whose i-th entry is self[indices[i]]. Out-of-bounds indices surface as IndexOutOfBounds(idx). The result is canonicalised onto the Numeric fast path when the gathered rows leave an Int / Float column all-valid (the same invariant gather_series keeps), so the backend is a function of the gathered content, not the source.

#
Series::get

Read cell i as a Scalar (Null for null cells). Out-of-bounds indices bubble the underlying IndexOutOfBounds error.

#
Series::head

fn Series::head(self : Series, n : Int) -> Series

The first n cells, or every cell when n exceeds the length — the Series twin of DataFrame::head. A negative n clamps to an empty series. Total.

#
Series::is_empty

fn Series::is_empty(self : Series) -> Bool

true when the series has zero cells.

#
Series::is_null

fn Series::is_null(self : Series, i : Int) -> Bool raise
DataError

Whether cell i is null. Out-of-bounds indices bubble the underlying IndexOutOfBounds error.

#
Series::last

Last cell as a Scalar, in row order — the trailing mirror of first, with the same positional (skip-nothing) rule and Scalar::Null for an empty series or a trailing null cell.

#
Series::len

fn Series::len(self : Series) -> Int

Number of cells (valid plus null).

#
Series::max

Maximum non-null cell as a Scalar (Polars' Series.max). Mirrors min with the order reversed; the Float NaN and empty-series rules are identical. Total — unlike the Result-wrapped form this replaces.

#
Series::mean

Arithmetic mean of non-null cells, returned as Double.

Int columns accumulate the numerator in Double (the mean's output type), so a large-magnitude column cannot wrap past 2^63 and flip the mean's sign; Float columns accumulate in Double. The division is performed in Double. (Series::sum, by contrast, keeps the Int dtype and so accumulates in Int64, overflowing past 2^63.)

The denominator is the non-null count (Series::count), so a Float NaN is a present value that both counts toward the divisor and propagates: any non-null NaN makes the mean NaN (Polars semantics; only Null is skipped).

  • Empty / all-null numeric series → raise InvalidOperation(...).
  • Non-numeric series → raise TypeMismatch(...).

#
Series::median

fn Series::median(self : Series) -> Double raise
DataError

Median of the non-null cells, as Double — the middle of the sorted values, or the mean of the two middles for an even count (Polars). Numeric only (Int widens to Double). Unlike sum / mean, a NaN is skipped (treated as missing, the order-statistic rule sort / min / max follow).

  • Empty / all-null / all-NaN series → raise InvalidOperation(...).
  • Non-numeric series → raise TypeMismatch(...).

#
Series::min

Minimum non-null cell as a Scalar, by the natural order of the series' dtype (Polars' Series.min). Total — every dtype has an order, so it never fails (unlike the Result-wrapped form this replaces).

  • Empty / all-null → Scalar::Null.
  • Float NaN is skipped (treated as missing), matching sort and Polars' regular min / max, which ignore NaN — distinct from the propagating nan_min / nan_max. (This differs from sum / mean, where NaN propagates — exactly as in Polars.) A series of only NaN (and/or nulls) → Scalar::Null.
  • Bool order: false < true.

#
Series::n_unique

fn Series::n_unique(self : Series) -> Int

Number of distinct non-null values. Cells are keyed by the same composite-key normalisation group_by / join use (key_cell), so the distinct count agrees with grouping cell-for-cell: two cells collide exactly when they would share a group_by group. Two Float normalisations follow from that shared key — every NaN collapses into one bucket (it is a value, not missing, so it is one distinct value, the rule that also puts every NaN key in one group_by group), and -0.0 folds into +0.0 (they are IEEE-equal and share a group, hence one distinct value). Keying by Scalar::to_string would instead let the -0.0 rendering split them, diverging from group_by.

#
Series::name

fn Series::name(self : Series) -> String

Column name.

#
Series::not_equal

fn Series::not_equal(x : Series, y : Series) -> Bool

#
Series::null_count

fn Series::null_count(self : Series) -> Int

Number of null cells.

#
Series::rename

fn Series::rename(self : Series, new_name : String) -> Series

Return a copy of this series with a different column name. Storage is shared (immutable), so this is O(1).

#
Series::reverse

fn Series::reverse(self : Series) -> Series

The cells in reverse order, name unchanged — Polars' Series.reverse. Total.

#
Series::slice

fn Series::slice(self : Series, start : Int, end : Int) -> Series raise
DataError

Half-open [start, end) slice. Bounds checks mirror BuiltinColumn::slice; errors surface unchanged so callers receive the same IndexOutOfBounds / InvalidOperation diagnostics.

#
Series::sort

Sort the series' own values, returning a new series with the same name — Polars' Series.sort. order defaults to ascending and nulls to NullsLast, and both mean exactly what they mean for DataFrame::sort: the same kernel resolves the column, so a Float NaN counts as missing alongside Null (the repository's deliberate ordering convention) and ties keep their input order — the sort is stable.

Total: every dtype has an order, and the permutation indexes the series itself, so nothing can fail.

#
Series::std

Sample standard deviation (ddof = 1, Polars' default) of the non-null cells, as Double — the square root of variance. Numeric only (Int widens to Double). Computed by Welford's algorithm, so a finite-variance window of near-Double-max values still yields a finite result; a NaN is a present value that propagates (only Null is skipped).

  • Fewer than two non-null cells (empty, single-value, all-null) → raiseInvalidOperation(...): the sample denominator cnt - 1 has no value.
  • Non-numeric series → raise TypeMismatch(...).

#
Series::sum

Sum of non-null cells.

  • Int series → Scalar::Int(sum), accumulated in 64-bit Int64 (so only sums past 2^63 overflow, not the old 32-bit ceiling).
  • Float series → Scalar::Float(sum), accumulated in 64-bit Double. A NaN cell is a valid value, not missing, so it participates and propagates: any non-null NaN makes the sum NaN (Polars semantics; only Null is skipped).
  • Empty / all-null numeric series → Scalar::Int(0) or Scalar::Float(0.0) (additive identity). An all-NaN Float series sums to NaN, not the identity — every cell is a present value.
  • Non-numeric series (Bool, String) → raise TypeMismatch.

#
Series::tail

fn Series::tail(self : Series, n : Int) -> Series

The last n cells, or every cell when n exceeds the length — the Series twin of DataFrame::tail. A negative n clamps to an empty series. Total.

#
Series::to_repr

#
Series::to_scalars

Materialise every cell as a Scalar (Null for null cells), in order. Total — reads the backing array and validity mask once. Renderers (CSV / JSON / Markdown) use this to walk a column without a per-cell bounds-checked get.

#
Series::variance

fn Series::variance(self : Series) -> Double raise
DataError

Sample variance (ddof = 1) of the non-null cells, as Doublestd squared, sharing its rules (numeric only, NaN propagates, fewer than two non-null cells raise InvalidOperation).