Skip to content

Tables and dataframes

Arrow table ports provide a consistent interchange format even when different blocks use different processing libraries. Choose one library inside a block and keep its methods and expressions consistent.

Grouping syntax by library

Grouping is a common source of generated-script errors because the method name is different in each library.

Library Correct grouping syntax
PyArrow table.group_by(keys).aggregate(specs)
Polars frame.group_by(keys).agg(expressions)
pandas frame.groupby(keys).agg(expressions)
R dplyr dplyr::group_by(frame, ...) followed by dplyr::summarise()

groupBy is not valid for PyArrow, Polars, pandas, or dplyr.

PyArrow

ft.input_table returns a pyarrow.Table, making PyArrow the most direct choice when the required operation is supported.

import pyarrow.compute as pc

import flasktrack as ft

rows = ft.input_table("rows")
clean = rows.filter(pc.greater_equal(rows["value"], 0))
summary = clean.group_by("sample_id").aggregate(
    [("value", "mean"), ("value", "count")]
)
ft.output_table("summary", summary)

Polars

Convert once, process with Polars, and pass the final dataframe directly to output_table.

import polars as pl

import flasktrack as ft

rows = pl.from_arrow(ft.input_table("rows"))
summary = (
    rows
    .filter(pl.col("value").is_not_null())
    .group_by("sample_id")
    .agg(
        pl.col("value").mean().alias("value_mean"),
        pl.len().alias("observation_count"),
    )
)
ft.output_table("summary", summary)

pandas

Convert to pandas only when its API is needed. For large inputs, this may use more memory than processing the Arrow table directly.

import flasktrack as ft

rows = ft.input_table("rows").to_pandas()
summary = (
    rows.dropna(subset=["value"])
    .groupby("sample_id", as_index=False)
    .agg(value_mean=("value", "mean"), observation_count=("value", "size"))
)
ft.output_table("summary", summary)

R with dplyr

source("/opt/flasktrack/flasktrack.R")

rows <- ft_input_table("rows")
summary <- rows |>
  dplyr::filter(!is.na(.data$value)) |>
  dplyr::group_by(.data$sample_id) |>
  dplyr::summarise(
    value_mean = mean(.data$value),
    observation_count = dplyr::n(),
    .groups = "drop"
  )

ft_output_table("summary", summary)

Validate required columns

Fail early with a clear list of missing columns rather than allowing a later method call to fail ambiguously.

Python

required = {"sample_id", "value"}
missing = required.difference(rows.column_names)
if missing:
    raise ValueError(f"Missing required columns: {sorted(missing)}")

R

required <- c("sample_id", "value")
missing <- setdiff(required, names(rows))
if (length(missing) > 0L) {
  stop(paste("Missing required columns:", paste(missing, collapse = ", ")))
}

Also verify that numeric, timestamp, and categorical columns have the types your calculation expects.