Motivation
Many data workflows require human review before publication. Official statistics, digital humanities, archives, machine learning, and data engineering all rely on iterative assessment of candidate values before they become part of a released dataset.
The goal of review is to make semantic review explicit and reproducible. Rather than overwriting existing values, each review round creates a new version of one or more reviewable claims while preserving the previous versions and their provenance. This review history can be inspected, reproduced, and extended with additional review rounds.
Creating reviewable claims
The review package provides four verbs for reproducible semantic review:
-
claims_df()creates reviewable claims. -
review()allocates a review round and labels the intended review task. -
document()records how the review was carried out. -
approve()approves the reviewed values as the current candidate claims while preserving the complete review history.
claims <- revisions(
Orange,
scope_var = "age",
subject_var = "Tree"
)
head(claims, n = 6)
#> claim_id age Tree circumference_candidate
#> 1 1 118 1 30
#> 2 2 484 1 58
#> 3 3 664 1 87
#> 4 4 1004 1 115
#> 5 5 1231 1 120
#> 6 6 1372 1 142revisions() constructs a semantically enriched
claims_df tibble that separates variables into three
structural roles and one or more reviewable variables.
-
Identifieruniquely identifies each claim. -
Scopedefines the context in which the claim is made. -
Subjectidentifies the entity being described. -
Reviewablevariables contain the values that may change during semantic review.
The structural variables remain stable throughout the review process, while reviewable variables may acquire successive reviewed versions.
Readers familiar with the tidy data principles will recognise this separation. Structural variables play a role analogous to identifiers, dimensions, and attributes in statistical data production, while the reviewable variables correspond to measured values that are iteratively reviewed and improved. The structural variables provide the context for interpretation and allow claims to be grouped, filtered, or compared, whereas the reviewable variables capture the semantic content that evolves during the review process.
names(claims)
#> [1] "claim_id" "age"
#> [3] "Tree" "circumference_candidate"Notice that circumference has become
circumference_candidate. Candidate values are the current
working version of each reviewable variable and form the starting point
for the first review round.
First review round
A review round allocates a new review column for a reviewable variable and records a label describing the intended review task. The label serves as an instruction before the review is carried out and later identifies the completed review activity.
reviewed <- claims |>
review(
"circumference",
review_id = "remeasure",
label = "Remeasure the circumference of each tree."
)
attr(reviewed, "review_label")
#> remeasure
#> "Remeasure the circumference of each tree."The review algebra is independent of the review interface. Reviewers may use whatever environment best suits the task, provided that the reviewed values are written back into the allocated review columns.
The package deliberately does not prescribe how the review is carried out. For example, the review may be
- a
dplyr::mutate()pipeline, - values imported from a reviewed CSV file,
- an interactive R console session,
- a Shiny application,
- an Excel or LibreOffice spreadsheet,
- an OpenRefine workflow.
For illustration we edit one value directly.
reviewed$circumference_review_1[1] <- 31Once the review values have been entered, the review round can be documented.
reviewed <- reviewed |>
document(
revision = "circumference_remeasure",
agent = person("Jane", "Doe", role = "rev"),
used = "doi:10.5281/zenodo.1234567",
comment = "Verified against the laboratory notebook."
)
attr(reviewed, "prov_comment")
#> circumference_remeasure
#> "Verified against the laboratory notebook."document() records how the review was performed, who
performed it, which evidence or resources informed the review, and
optional reviewer comments documenting how the review task was
interpreted or why particular decisions were made.
Provenance is stored separately from the reviewed values, allowing the same review data to be interpreted or exported in different provenance models.
Readers familiar with FAIR data principles, reproducible research, or statistical and digital heritage workflows may recognise a common problem: as data are cleaned, harmonised, and reviewed, an increasing share of the knowledge about why particular decisions were made remains only in the analyst’s or curator’s head. The resulting datasets may be reusable, but the review process itself is often difficult to inspect, reproduce, or audit.
The review algebra aims to record as much of this review history as possible while requiring as little additional documentation effort as possible. By separating reviewed values from the provenance of the review, it records which agents (people, software, or AI models) performed which activities, and what entities (datasets, publications, files, or other resources) informed those decisions.
review() and document() deliberately
separate the description of a review task from the documentation of its
execution.
A review label describes what reviewers are asked to do. It therefore serves as a task description before the review and as the label of the completed review activity afterwards.
A reviewer comment explains how the task was interpreted or why particular review decisions were made.
This lightweight provenance layer follows the concepts of the PROV data model without requiring users to work directly with RDF or ontologies. It can later be serialised to PROV-O or aligned with standards such as SDMX, DataCite, or archival provenance models. The objective is to improve the reviewability, auditability, reproducibility, and ultimately the trustworthiness and reusability of reviewed data while remaining compatible with ordinary R data frames.
Complete Review Workflow
claims <- claims_df(
Orange,
scope_var = "age",
subject_var = "Tree"
) |>
review("circumference",
review_id = "remeasure"
)
claims$circumference_remeasure[1] <- 31
claims <- claims |>
document(
revision = "circumference_remeasure",
agent = person("Jane", "Doe", role = "rev"),
used = "doi:10.5281/zenodo.1234567"
) |>
review("circumference",
review_id = "OpenRefine"
)
claims$circumference_OpenRefine[1] <- 32
claims <- claims |>
document(
revision = "circumference_OpenRefine",
agent = utils::person("Joe", "Doe", role = "ctb"),
used = "doi:10.2908/NAMA_10_GDP"
) |>
approve()Adding subsequent rounds of review
Each review round is allocated from the current candidate values.
After approve(), those candidate values become the approved
result of the previous review, allowing further review rounds to
continue from the updated claims.
Consider this second-round review:
reviewed <- reviewed |>
review("circumference")Again, the review itself may take place using any suitable workflow.
reviewed$circumference_review_2[1] <- 32Each review round has its own provenance. Different reviewers, software, or evidence can therefore contribute to successive stages of the review.
reviewed <- reviewed |>
document(
revision = "circumference_review_2",
agent = "OpenRefine 3.10",
used = "doi:10.2908/NAMA_10_GDP"
)The review history is therefore
circumference_candidate
↓
circumference_review_1
↓
circumference_review_2
Finalising a review
Once the review is complete, the current reviewed values become the new candidate values. The review history is retained, allowing future review rounds to continue from the released version while preserving the complete review trail.
The review history is preserved, while future review rounds begin from the finalised candidate values.
names(approved)
#> [1] "claim_id" "age"
#> [3] "Tree" "circumference_candidate"
#> [5] "circumference_2" "circumference_remeasure"
#> [7] "circumference_review_1" "circumference_review_2"
#> [9] "circumference_approved"
head(approved, 6)
#> claim_id age Tree circumference_candidate circumference_2
#> 1 1 118 1 30 30
#> 2 2 484 1 58 58
#> 3 3 664 1 87 87
#> 4 4 1004 1 115 115
#> 5 5 1231 1 120 120
#> 6 6 1372 1 142 142
#> circumference_remeasure circumference_review_1 circumference_review_2
#> 1 30 31 32
#> 2 58 31 32
#> 3 87 31 32
#> 4 115 31 32
#> 5 120 31 32
#> 6 142 31 32
#> circumference_approved
#> 1 30
#> 2 58
#> 3 87
#> 4 115
#> 5 120
#> 6 142The complete review algebra therefore consists of four operations:
create reviewable claims, allocate review
rounds, document (explain) each review, and promote the
current review into the next candidate version.
Dual structure and complete workflow
The review algebra can be viewed as a dual extension of tidy data.
An ordinary tidy data frame represents observations in rows and
variables in columns. The tidyverse provides an expressive algebra for
transforming these observations through operations such as
mutate(), filter(), select(),
join(), and pivot_*().
The review algebra introduces a second algebra that is structurally aligned with the tidy data algebra. The upper layer contains successive semantic states of the same claim vectors (reviewable variables), while the lower layer records provenance describing with statements about the transitions between those states how the transition took place.
REVIEW MATRIX
┌──────────────────────────────────────────────────────┐
│ Upper layer: review states │
│ │
│ candidate → review 1 → review 2 → ... → reviewed │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Lower layer: provenance │
│ │
│ activity • agent • resources • comments │
└──────────────────────────────────────────────────────┘
The two layers share the same structure. Every review column in the upper layer has a corresponding provenance description in the lower layer. This shared structure makes it possible to derive provenance automatically at different levels of detail.
A single provenance statement may describe an entire review column:
“The bibliographic information was verified by Librarian 1.”
or, if required, the provenance can be refined to individual claims:
“Rows 001, 004 and 005 were corrected after consulting manuscript B.”
Because both layers share the same identifiers and ordering, column-level provenance and cell-level provenance are different resolutions of the same underlying model.
Review sequencing
Review sequencing
A review column represents a semantic mutation of a reviewable variable.
Unlike ordinary mutate() operations, review mutations
are ordered because their sequence may affect the final result. For
example, a two-stage imputation or a sequence of authority-file
reconciliations is generally not commutative.
For this reason, every review column belongs to a strictly ordered review chain.
A complete workflow may consist of several reviews or refinements.
review algebra
bibliography_candidate
│
▼
bibliography_author_year
│
▼
bibliography_publisher
│
▼
bibliography_reviewed
provenance algebra
author_year
activity
agent
used
...
publisher
activity
agent
used
The column names provide meaningful identifiers for the review stages, whereas the package records their ordering internally. This allows review stages to be named according to their intended purpose while preserving the temporal sequence of the review.
Each review stage therefore has two complementary descriptions.
A label records the intended review activity before it
is carried out. An explanation records what actually happened during the
review.
The label expresses the planned activity:
“Verify authors and publication years.”
whereas the explanation documents its execution in an optional comment:
“Verified by Librarian 1 using the National Bibliography. Three publication years were corrected.”
This distinction mirrors the distinction in provenance models between an activity specification and the execution of that activity.
Relation to Supply and Use and Input-Output Tables
Readers familiar with Supply and Use Tables (SUTs) or Symmetric Input-Output Tables (SIOTs) may recognise a similar design pattern.
Supply and Use Tables (SUTs) and Symmetric Input-Output Tables (SIOTs) consist of several matrices that describe different aspects of the same economy while sharing a common ordering of industries and products. Because these matrices are structurally aligned, aggregates such as gross domestic product (GDP) become deterministic functions of the detailed accounts (f.e., total consumption, total import.) Changes in one part of the system therefore propagate consistently through the others.
The review algebra applies the same principle to semantic review. The upper matrix records successive semantic states of a tidy data frame, while the lower matrix records the provenance associated with those states. Because the two matrices share the same structure, provenance can be generated at the level of entire review stages or, when necessary, for individual reviewed claims.
Unlike many review systems, review does not store
separate audit logs or provenance graphs as primary data structures.
Instead, it stores successive semantic states of reviewable variables
together with lightweight provenance describing each review stage. Audit
reports, provenance graphs, release notes, and cell-level revision
histories are deterministic projections of this representation rather
than independently maintained artefacts.
Garamantas example
The review algebra is independent of the application domain. The same workflow can be applied to statistical observations, archival metadata, cultural heritage collections, or machine-generated annotations.
garamantas <- data.frame(
resources = c(
"https://garamantas.lv/en/file/475833",
"https://garamantas.lv/en/file/471397",
"https://garamantas.lv/en/file/475825"
),
instance_of = rep("photograph", 3),
depicts = rep("building", 3)
)
garamantas_claims <- revisions(
garamantas,
scope_var = "instance_of",
subject_var = "resources"
) |>
review(c("depicts"))
garamantas_claims$depicts_1[2] <- "group of people"Although the domain differs, the review algebra remains unchanged:
revisions()
↓
review()
↓
document()
↓
approve()
↓
next_revisions()
garamantas_reviewed <- garamantas_claims |>
document(
revision = "depicts_1",
agent = person("Alice", "Curator", role = "rev"),
used = "doi:example001"
) |>
approve()
print(garamantas_reviewed)
#> claim_id instance_of resources depicts_candidate
#> 1 1 photograph https://garamantas.lv/en/file/475833 building
#> 2 2 photograph https://garamantas.lv/en/file/471397 building
#> 3 3 photograph https://garamantas.lv/en/file/475825 building
#> depicts_1 depicts_approved
#> 1 building building
#> 2 group of people group of people
#> 3 building building
attributes(garamantas_reviewed)
#> $names
#> [1] "claim_id" "instance_of" "resources"
#> [4] "depicts_candidate" "depicts_1" "depicts_approved"
#>
#> $row.names
#> [1] 1 2 3
#>
#> $id
#> [1] "claim_id"
#>
#> $scope
#> [1] "instance_of"
#>
#> $subject
#> [1] "resources"
#>
#> $reviewable
#> [1] "depicts"
#>
#> $prov_id
#> [1] "candidate" "depicts_1"
#>
#> $prov_activity
#> candidate depicts_1
#> "create" NA
#>
#> $prov_agent
#> candidate depicts_1
#> NA "Alice Curator [rev]"
#>
#> $prov_used
#> candidate depicts_1
#> NA "doi:example001"
#>
#> $prov_comment
#> depicts_1
#> NA
#>
#> $review_label
#> 1
#> NA
#>
#> $review_sequence
#> candidate 1
#> 0 1
#>
#> $class
#> [1] "reviewed_df" "claims_df" "data.frame"
#>
#> $approval_activity
#> [1] "approval"
#>
#> $approval_agent
#> [1] NAConclusion
The review package provides a human-centric semantic review system for data that can be normalised into tidy tabular form. Whether observations originate from spreadsheets, relational databases, file systems, RDF graphs, or other structured sources, and represent statistical, digital humanities, or natural science observations, the review algebra remains unchanged once they have been represented as reviewable claims.
Observation layer
│
├── data.frame
├── CSV
├── RDF
├── File system
└── Database
│
▼
revisions()
│
▼
Semantic stabilisation
review()
document()
approve()
│
▼
Stable semantic object
│
├── RDF
├── Frictionless
├── Data frame
└── SQL
The package does not attempt to replace existing data
engineering,
transformation, or knowledge representation tools. Instead, it adds a
lightweight
semantic review layer that can be inserted into existing tidyverse
workflows.
Its aim is to make semantic review reproducible by recording review
states,
review activities, and their provenance while remaining compatible with
ordinary
data-frame operations.
The central design principle is that review should be independent of
the
original data source. Any resource that can be normalised into
reviewable tidy
claims can be reviewed using the same workflow. This makes the review
algebra
equally applicable to conventional tabular data, flattened RDF
graphs,
filesystem observations, and other structured resources, while leaving
more
complex graph structures or unstructured documents to specialised
review
approaches.
The package provides a vectorised review workspace for creating,
reviewing,
documenting, and finalising semantic claims without prescribing how
reviews are
performed. Review activities may take place entirely within R or be
delegated to
external tools such as spreadsheets, databases, or interactive
review
interfaces before the reviewed claims are returned to the workflow.
Although review is not an ontology or knowledge graph
toolkit, its design is
informed by provenance, policy, and statistical quality-control
models,
allowing review histories to be audited, serialised, and exchanged in
a
reproducible form. The accompanying vignette From Review
Algebra to Provenance Modelling describes how review activities can
be represented using lightweight
provenance metadata.
Once a review has been completed, the resulting semantic claims can
be
transformed into the representation most appropriate for
publication,
preservation, or exchange. The companion dataset package package
provides an
R-native representation for describing and serialising released
datasets,
supporting reproducible and interoperable publication using existing
metadata
standards.