# Aggregates

Every Event carries Aggregates: for each Channel its Rule aggregates, the minimum, maximum and mean over the Samples of the Event, and more. They tell you what the data looked like while the Event ran, without keeping the Samples yourself.

## Which Channels

An Event carries the Aggregates of the Channels its Rule's Conditions use, plus those named with `aggregate(...)` (see [Aggregates for more Channels](https://github.com/romanhaendler/rbed#aggregates-for-more-channels-aggregate)). Read them with `event.aggregates(x)`.

Reading a Channel the Rule does not aggregate throws an `IllegalArgumentException` that names the missing `.aggregate(...)`, so a forgotten declaration is never mistaken for "no data".

## A vehicle's drives

`Drive` is an Event while `speed` is above a threshold. Its Condition uses `speed`, so `speed` is aggregated anyway; `.aggregate(temp)` adds the engine temperature. The log is irregular: a Sample every 2 seconds while the speed changes, every 10 seconds otherwise.

Parameters (in the page address as ?threshold=…):

- Driving above (`threshold`): 0 to 13 m/s in steps of 1, default 1

The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')

Java source at the default parameters:

```java
import io.github.romanhaendler.rbed.*;
import java.util.Map;

Channel speed = Channel.named("speed");   // m/s
Channel temp = Channel.named("temp");     // engine temperature, °C

Rule drive = Rule.named("Drive")
    .when(speed.above(1))
    .aggregate(temp);                     // speed is aggregated anyway
Detector detector = Detector.builder()
    .rules(drive)
    .listener(new EventListener() {
      @Override public void onEnded(Event e) {
        Aggregates s = e.aggregates(speed);
        System.out.printf("avg %.1f m/s, max %.1f m/s, %.0f m driven%n",
            s.timeWeightedMean(), s.max(), s.integral());
      }
    })
    .build();

double[][] log = {   // time (s), speed (m/s), engine temperature (°C)
  {0, 0, 60}, {10, 0, 60}, {20, 0, 60}, {22, 3, 61}, {24, 6, 62}, {26, 9, 63}, {28, 12, 64},
  {30, 14, 65}, {40, 14, 70}, {50, 14, 74}, {60, 14, 77}, {62, 11, 77}, {64, 8, 77},
  {66, 5, 77}, {68, 2, 76}, {70, 0, 76}, {80, 0, 74}, {90, 0, 72}, {100, 0, 70},
  {102, 3, 70}, {104, 6, 71}, {106, 8, 71}, {116, 8, 73}, {126, 8, 75}, {128, 5, 75},
  {130, 2, 75}, {132, 0, 75}, {140, 0, 74}, {150, 0, 73},
};
for (double[] row : log) {
  detector.push((long) row[0] * 1_000, Map.of(speed, row[1], temp, row[2]));
}
detector.close();
```

At the default threshold of 1 m/s there are two Drives, from 22 s to 70 s and from 102 s to 132 s.

### Things to try

- Raise *Driving above* to 10 m/s: the second drive, never faster than 8 m/s, is no Drive any more, and the first one is shorter, from 28 s to 64 s. Its Aggregates change with its span: its mean speed rises.

The Event table on every page of this site shows each Event's minimum, maximum and mean per Channel under the Event.

## The components

- `min`, `max`, `mean`: over the Samples of the Event in which the Channel had a value.
- `count`: the number of those Samples. If it is 0, `min`, `max` and `mean` are NaN: the site's Event tables then say "no value", and the full Aggregates show a dash.
- `timeWeightedMean`: the mean with each value weighted by how long it held, until the next Sample, time advance or the Event's end.
- `integral`: value × seconds, time-weighted like the mean: distance from speed, energy from power, dose from rate.

The plain `mean` treats every Sample alike, which is right for regularly sampled data. With irregular sampling, as in the log above, `timeWeightedMean` gives the true average over time. At the default threshold, the first Drive's Samples average 9.33 m/s, but the vehicle spent most of the Drive at 14 m/s: its time-weighted mean is 11.67 m/s, and its integral of 560 is the distance it covered, in metres.

The time-weighted values are NaN if the Channel had no value during the Event, and the time-weighted mean also for an Event of zero length.

## While an Event runs

The listener receives the Event as it stands at each stage of its lifecycle. Aggregates of the Pending and the Confirmed Event cover the Event so far; those of the Ended Event cover it from its start to its end.

Another Rule can use them in its Conditions: `latest(rule).min(x)`, `.max(x)` and `.mean(x)` are the Aggregates of the most recent Confirmed Event of `rule` (see [The latest Event](https://romanhaendler.github.io/rbed/latest/)).

## Reference

- [Aggregates](https://github.com/romanhaendler/rbed#aggregates) and [Aggregates for more Channels](https://github.com/romanhaendler/rbed#aggregates-for-more-channels-aggregate) in the README, the canonical text
- [Aggregates](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Aggregates.html) and [Event](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html) in the Javadoc

Methods used: [`Channel.named`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Channel.html#named%28java.lang.String%29), [`Rule.named`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#named%28java.lang.String%29), [`Rule.when`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#when%28io.github.romanhaendler.rbed.Condition%29), [`Expression.above`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#above%28double%29), [`Rule.aggregate`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#aggregate%28io.github.romanhaendler.rbed.Channel...%29), [`Detector.builder`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#builder%28%29), [`Detector.Builder.rules`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#rules%28io.github.romanhaendler.rbed.Rule...%29), [`Detector.Builder.listener`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#listener%28io.github.romanhaendler.rbed.EventListener%29), [`EventListener.onEnded`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html#onEnded%28io.github.romanhaendler.rbed.Event%29), [`Event.aggregates`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html#aggregates%28io.github.romanhaendler.rbed.Channel%29), [`Aggregates.timeWeightedMean`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Aggregates.html#timeWeightedMean%28%29), [`Aggregates.max`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Aggregates.html#max%28%29), [`Aggregates.integral`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Aggregates.html#integral%28%29), [`Detector.Builder.build`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#build%28%29), [`Detector.push`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#push%28long,java.util.Map%29), [`Detector.close`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#close%28%29).
