Statistics

Adama has statistical aggregation capabilities for working with collections of numeric data. These tie into the query system so you can compute summary statistics over tables and lists reactively -- the stats update automatically when the underlying data changes.

Aggregation with Reduce

The main tool for statistical operations is the reduce keyword paired with lambda functions. You group data and then compute aggregations over each group.

Basic Counting

Count items in a group:

record Task {
  public int id;
  public string status;
  public string assignee;
}

table<Task> _tasks;

// Count tasks by status
public formula tasks_by_status =
  iterate _tasks
  reduce on status via (@lambda items: items.size());

Size for Counting

The size() method on lists is the most direct way to count things:

record Player {
  public int id;
  public bool active;
}

table<Player> _players;

// Total player count
public formula total_players = (iterate _players).size();

// Active player count
public formula active_players = (iterate _players where active == true).size();

// Inactive player count
public formula inactive_players = (iterate _players where active == false).size();

Numeric Aggregations

Field Projection with .sum() and .average()

The most direct way to aggregate numeric fields -- project the field and call .sum() or .average():

record Sale {
  public int id;
  public double amount;
  public string category;
}

table<Sale> _sales;

// Sum a numeric field
public formula total_sales = (iterate _sales).amount.sum();

// Average a numeric field
public formula avg_sale = (iterate _sales).amount.average();

// Sum with default value (since sum returns maybe<T>)
public formula safe_total = (iterate _sales).amount.sum().getOrDefaultTo(0.0);
Note

.sum() and .average() return maybe<T> because the list might be empty. Use .getOrDefaultTo() to provide a fallback value when you need a concrete number.

Computing Sums with Reduce

For more complex aggregations, use reduce:

record Sale {
  public int id;
  public double amount;
  public string category;
}

table<Sale> _sales;

// Group and aggregate
public formula sales_by_category =
  iterate _sales
  reduce on category via (@lambda items: items);

Grouping and Analysis

Group By Operations

reduce gives you SQL-like GROUP BY:

record Order {
  public int id;
  public string customer;
  public string product;
  public int quantity;
  public double price;
}

table<Order> _orders;

// Group orders by customer
public formula orders_by_customer =
  iterate _orders
  reduce on customer via (@lambda orders: orders);

// Group orders by product
public formula orders_by_product =
  iterate _orders
  reduce on product via (@lambda orders: orders);

// Count orders per customer
public formula order_counts =
  iterate _orders
  reduce on customer via (@lambda orders: orders.size());

Multi-Level Grouping

You can chain operations for multi-level analysis:

record Event {
  public int id;
  public date eventDate;
  public string type;
  public int attendees;
}

table<Event> _events;

// Group events by type
public formula events_by_type =
  iterate _events
  reduce on type via (@lambda events: events);

Working with Formulas

Formulas give you reactive computation -- they automatically recompute when the underlying data changes. This is where statistics in Adama get interesting, because your aggregates are always live:

record Transaction {
  public int id;
  public double amount;
  public bool completed;
}

table<Transaction> _transactions;

// Count completed transactions
public formula completed_count =
  (iterate _transactions where completed == true).size();

// Count pending transactions
public formula pending_count =
  (iterate _transactions where completed == false).size();

// Total transaction count
public formula total_count = (iterate _transactions).size();

Practical Examples

Leaderboard Statistics

record Player {
  public int id;
  public string name;
  public int score;
  public int gamesPlayed;
}

table<Player> _players;

// Player count
public formula player_count = (iterate _players).size();

// Top scorers
public formula top_ten =
  iterate _players
  order by score desc
  limit 10;

// Players with games
public formula active_players =
  (iterate _players where gamesPlayed > 0).size();

Inventory Tracking

record Item {
  public int id;
  public string category;
  public int quantity;
  public double price;
}

table<Item> _inventory;

// Items by category
public formula items_by_category =
  iterate _inventory
  reduce on category via (@lambda items: items.size());

// Low stock items
public formula low_stock =
  (iterate _inventory where quantity < 10).size();

// Out of stock items
public formula out_of_stock =
  (iterate _inventory where quantity == 0).size();

Time-Based Analysis

record Log {
  public int id;
  public date logDate;
  public string level;
  public string message;
}

table<Log> _logs;

// Logs by level
public formula logs_by_level =
  iterate _logs
  reduce on level via (@lambda logs: logs.size());

// Error count
public formula error_count =
  (iterate _logs where level == "ERROR").size();

// Warning count
public formula warning_count =
  (iterate _logs where level == "WARNING").size();

Aggregation Patterns

Conditional Counting

record Task {
  public int id;
  public bool completed;
  public string priority;
}

table<Task> _tasks;

// High priority incomplete tasks
public formula urgent_tasks =
  (iterate _tasks where completed == false && priority == "HIGH").size();

// Completed task percentage requires computing both counts
public formula total_tasks = (iterate _tasks).size();
public formula done_tasks = (iterate _tasks where completed == true).size();

Existence Checks

record User {
  public int id;
  public string role;
}

table<User> _users;

// Check if any admins exist
public formula has_admins =
  (iterate _users where role == "admin" limit 1).size() > 0;

// Check if user pool is empty
public formula no_users = (iterate _users).size() == 0;

Method Summary

List Aggregation

Operation Method Returns
Count elements .size() int
Sum a field (iterate tbl).field.sum() maybe<T>
Average a field (iterate tbl).field.average() maybe<T>
Group by field reduce on field via lambda map<K,V>

Common Patterns

Goal Pattern
Total count (iterate table).size()
Filtered count (iterate table where condition).size()
Group counts iterate table reduce on field via (@lambda x: x.size())
Existence check (iterate table where condition limit 1).size() > 0
Empty check (iterate table).size() == 0

Performance

A few things worth keeping in mind:

  1. Use limit when checking existence. limit 1 stops iterating after the first match rather than scanning the whole table.
  2. Index fields used in where clauses. Add index to record fields you filter on frequently for faster lookups.
  3. Formulas are reactive -- they recompute when data changes, so your statistics stay current without you doing anything.
record Indexed {
  public int id;
  public string category;
  public int value;

  index category;  // Indexed for fast filtering
}
Previous Collections
Next Globals