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);
.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:
- Use
limitwhen checking existence.limit 1stops iterating after the first match rather than scanning the whole table. - Index fields used in
whereclauses. Addindexto record fields you filter on frequently for faster lookups. - 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
}