diff --git a/src/explanation/entity-integrity.md b/src/explanation/entity-integrity.md index b400dd06..2bb05b83 100644 --- a/src/explanation/entity-integrity.md +++ b/src/explanation/entity-integrity.md @@ -223,6 +223,30 @@ The rule applies whether the table has only new attributes or also inherits attributes through foreign keys. A new primary-key attribute means a new dimension. +### Grain + +A dimension is a *single* axis of variation; a table's **grain** is the *whole +combination* of axes at which each row is recorded or computed — its full primary +key. Grain is the answer to this guide's leading question, *"what is one row of +this table?"* + +> **The grain of a table is the level of detail at which its data is recorded or +> computed: what one row represents, given by the combination of dimensions in its +> primary key.** + +- `Session`, keyed `(subject_id, session_idx)`, has grain **(subject × session)** — + one row per session of a subject. +- Adding a dimension makes the grain **finer**: a part table that adds `blob_idx` + (`Detection.Blob` below) has grain **(… × blob)** — one row per blob of a + detection. +- A computed table's grain is the combination of dimensions at which its `make()` + produces rows — its + [`key_source`](../reference/specs/autopopulate.md). *A computation operates at + its grain.* + +"Grain" is the established term in dimensional modeling for what a single row +represents; DataJoint generalizes it from recorded fact rows to *computed* rows. + ### Tables that introduce dimensions ```python diff --git a/src/reference/specs/autopopulate.md b/src/reference/specs/autopopulate.md index 52273d37..3d9a8155 100644 --- a/src/reference/specs/autopopulate.md +++ b/src/reference/specs/autopopulate.md @@ -82,6 +82,8 @@ class Analysis(dj.Computed): The `key_source` property defines which entries should exist in the table—the complete set of primary keys that `make()` should be called with. +This set is the table's **grain**—the combination of dimensions at which the computation is done. `make()` runs once per key, so a computed table operates at the grain of its `key_source`. + ### 2.2 Automatic Key Source By default, DataJoint automatically calculates `key_source` as the join of all tables referenced by foreign keys in the primary key: diff --git a/src/reference/specs/query-algebra.md b/src/reference/specs/query-algebra.md index b949a1d5..e1c76fff 100644 --- a/src/reference/specs/query-algebra.md +++ b/src/reference/specs/query-algebra.md @@ -407,6 +407,8 @@ result = A.aggr(B, ..., exclude_nonmatching=True) # Only rows with matches **B must contain all primary key attributes of A.** This enables grouping B's rows by A's primary key. +Geometrically, aggregation **reduces the grain**: it projects the finer grain of `B` onto the coarser grain of `A` it contains, folding each group of B-rows into summary values. Requiring `B` to contain all of A's primary key is exactly what makes every B-row fall into one A-group. Note that `.proj` does *not* reduce the grain—it always retains the primary key (§3.2)—so aggregation, not projection, is the grain-reducing operator. + ### 5.4 Aggregate Functions ```python