Sign in

    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 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.

    The engine machinery the verbs build on is the row-rebuild machine (an index vector, a window, a mask, or a column merge turned into a new Series) and the storage-backend machine (converge / preserve / read / assert). Both are engine seams serving frame and the kernel layer, not Series API.

    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 except Date renders to its value form; never rejects a value, so the only failure is an unsupported side 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, Null, and Date targets raise Unsupported(_), and so does a Date source (a day count read as an integer is not a conversion this library offers — item / to_string render dates instead).

    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_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_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_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_scalars

    The one public nullable constructor — Polars' pl.Series(name, [1, None]) translated to a statically-typed language: the cells carry their own nullness as Scalar, and the column's dtype is dtype~ when given, or the dtype of the first non-null cell when not. The null-free from_ints / from_floats / from_bools / from_strings constructors stay the typed fast path; this is the door for cells that may be null — including Scalar::Date, which has no other public constructor.

    Every non-null cell must carry the target dtype exactly — an Int cell in a Float column is a cast, not a construction, so it raises TypeMismatch(Expected(target, got, name)) here (on the first offending cell, in cell order). Two targets cannot be inferred and raise instead: an all-null cells without dtype~ has nothing to decide from (InvalidOperation), and dtype~ = Null names no physical column (Unsupported). A nullable series lands on the general-purpose Builtin backend, like every from_*_options build.

    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) — the bracket spelling s[0] (Polars' s[0]). Out-of-bounds indices bubble the underlying IndexOutOfBounds error; a negative index does not wrap from the end (MoonBit indexing convention — the slice spelling s[-1:] is where negatives count from the end).

    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 keeps every cell but the last |n|, as Polars' head does. 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). Like sum / mean, a NaN propagates: one NaN cell makes the median NaN (the order statistics split — sort orders NaN as the largest value, min / max skip it).

    • Empty / all-null series → raise InvalidOperation(...); an all-NaN series yields NaN.
    • 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), as Polars' regular min / max ignore it — distinct from sum / mean, where NaN propagates (also as in Polars), and from sort, which orders NaN as the largest value. 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. Reads the series back to front through the same gather machine the window verbs above trim with — one spelling over the shared row-gather path, the Series twin of DataFrame::reverse.

    Series::slice

    fn Series::slice(self : Series, offset : Int, length? : Int) -> Series

    A length-first cell window — the Series twin of DataFrame::slice, Polars' Series.slice(offset, length). A negative offset counts from the end, saturating at the first cell; an omitted length runs to the end; a given one saturates at what remains, a negative one is empty. Total — never fails.

    Series::slice_between

    #alias("_[_:_]")
    fn Series::slice_between(self : Series, start? : Int, end? : Int) -> Series

    The bracket-slice sugar — the Series twin of DataFrame::slice_between, Polars' s[1:3] / s[2:] / s[:-1]: a half-open [start, end) cell window where an omitted bound runs to that end and a negative one counts from it, with crossed bounds an empty series. The bounds normalize (between_bounds) into the one (offset, length) window kernel slice runs, so the two spellings cannot disagree. Total — never fails. Reached as s[a:b].

    Series::sort

    fn Series::sort(self : Series, descending? : Bool, nulls_last? : Bool) -> Series

    Sort the series' own values, returning a new series with the same name — Polars' Series.sort. descending defaults to ascending and nulls_last to false — Polars' own default, which places missing cells first in an ascending sort — and both mean exactly what they mean for the corresponding DataFrame::sort key flags: the same kernel resolves the column, so a Float NaN orders by the IEEE total order — larger than every other value, trailing an ascending sort and leading a descending one, never re-routed through the null placement — 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, and an I64-riding dtype like Date) → 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 keeps every cell but the first |n|, as Polars' tail does. 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. Each present cell lifts through the dtype registry (scalar_of), so an I64-riding dtype like Date surfaces its own Scalar variant, not the buffer's.

    Series::variance

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

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