This vignette uses the real May-July 2026 sample offline. Its three
missing Dyfed-Powys submissions remain missing. Observed outcomes
describe West Yorkshire records only; neither missing submissions nor
missing outcome values are failures.
Three separate meanings of an outcome
any_action, arrest and
outcome_linked_to_object measure different things. An
action need not be an arrest, and a recorded link to the search object
is a separate source field. Searchlight preserves disagreements and
unknown values. Wilson intervals describe a binary outcome among
searches with that outcome observed. Unknown ethnicity appears in the
descriptive table.
hits <- sl_hit_rates(records, by = character())
display <- hits[hits$ethnicity_5 %in% c("Black", "White", "Unknown"), ]
knitr::kable(as.data.frame(display)[c("ethnicity_5", "outcome", "observed",
"missing_outcome", "hit_rate", "conf_low", "conf_high")], digits = 3)
| Black |
any_action |
58 |
0 |
0.379 |
0.266 |
0.508 |
| Unknown |
any_action |
2226 |
54 |
0.357 |
0.337 |
0.377 |
| White |
any_action |
2138 |
3 |
0.355 |
0.335 |
0.376 |
| Black |
arrest |
58 |
0 |
0.190 |
0.109 |
0.309 |
| Unknown |
arrest |
2226 |
54 |
0.132 |
0.119 |
0.147 |
| White |
arrest |
2138 |
3 |
0.196 |
0.179 |
0.213 |
| Black |
outcome_linked_to_object |
58 |
0 |
0.862 |
0.751 |
0.928 |
| Unknown |
outcome_linked_to_object |
2234 |
46 |
0.941 |
0.931 |
0.950 |
| White |
outcome_linked_to_object |
2141 |
0 |
0.917 |
0.904 |
0.928 |
Each outcome has a separate logistic comparison of Black versus White
searches, with force and object fixed effects. A constant control is
omitted and named in the output. Other ethnicities and unknown ethnicity
are excluded only from this specified pairwise model, with counts
retained in the exclusion table. An unstable, aliased or separated model
withholds its Wald interval instead of printing a misleading extreme
estimate.
coefficients <- attr(hits, "coefficients")
knitr::kable(coefficients[c("outcome", "odds_ratio", "conf_low", "conf_high",
"n", "status")], digits = 3)
| any_action |
1.082 |
0.630 |
1.858 |
2196 |
estimable |
| arrest |
0.991 |
0.506 |
1.942 |
2196 |
estimable |
| outcome_linked_to_object |
0.585 |
0.271 |
1.263 |
2199 |
estimable |
knitr::kable(coefficients[c("outcome", "omitted_constant_controls")])
| any_action |
force_id |
| arrest |
force_id |
| outcome_linked_to_object |
force_id |
| any_action |
2196 |
2280 |
178 |
3 |
| arrest |
2196 |
2280 |
178 |
3 |
| outcome_linked_to_object |
2199 |
2280 |
178 |
0 |
Hit rates condition on being searched. Differences in selection, the
distribution of unobserved risk, recording and the meaning of outcomes
can all affect them. Equal hit rates need not imply equal decision
thresholds, and differing hit rates do not by themselves establish
discrimination. Repeated searches also challenge independent-event
intervals when identifiers for clustering are unavailable. These
interpretation limits are related to the outcome-test framework of
Knowles, Persico and Todd (2001), doi:10.1086/318603 (open
working-paper version).
A time-quality screen is mandatory
The darkness design excludes every force-month marked unreliable or
lacking a timestamp audit. Source-clock midnight and London-clock
midnight are both checked upstream, so a date-only UTC timestamp shifted
to 01:00 in summer cannot pass. Passing this screen does not prove that
times are accurate.
| dyfed-powys |
2026-05 |
NA |
FALSE |
| dyfed-powys |
2026-06 |
NA |
FALSE |
| dyfed-powys |
2026-07 |
NA |
FALSE |
| west-yorkshire |
2026-05 |
0.010 |
TRUE |
| west-yorkshire |
2026-06 |
0.010 |
TRUE |
| west-yorkshire |
2026-07 |
0.014 |
TRUE |
date_only <- records[1:10, ]
day <- as.Date(date_only$date, tz = "Europe/London")
date_only$date <- as.POSIXct(paste(day, "00:00:00"), tz = "Europe/London")
date_only$date_raw <- format(date_only$date, "%Y-%m-%dT%H:%M:%S%z")
attr(date_only, "contract")$timestamps <- sl_timestamp_quality(date_only)
refused <- sl_veil_of_darkness(date_only)
#> Excluded 1 unreliable force-month(s).
knitr::kable(as.data.frame(refused)[c("odds_ratio", "n", "status")])
| NA |
0 |
insufficient outcome/predictor variation |
knitr::kable(attr(refused, "excluded_force_months"))
| west-yorkshire |
2026-05 |
FALSE |
Local clock time, sunset and civil twilight
Solar events are calculated for the published location and date with
suncalc, using Europe/London. For each distinct location
and year, the annual earliest and latest evening twilight define the
intertwilight clock window. This permits both daylight and darkness at
comparable clock times across the year. The default defines darkness
after civil dusk and removes the ambiguous sunset-to-dusk interval.
twilight = "sunset" provides an explicit alternative.
Missing locations are excluded, never imputed from a force centroid.
The logistic outcome is membership of the comparison ethnicity among
the selected pair. Predictors include darkness, a natural spline of
clock time (or a linear term with too few distinct times), weekday,
calendar month and force where these vary. Every exclusion step and the
final analysed rows can be inspected.
| 593.664 |
0.295 |
1195173 |
648 |
36 |
612 |
estimable |
| input |
4657 |
| reliable force-month time |
4657 |
| valid location and instant |
4496 |
| known comparison pair |
2122 |
| annual intertwilight clock window |
747 |
| unambiguous daylight or darkness |
648 |
| design window |
648 |
knitr::kable(attr(darkness, "excluded_force_months"))
This short summer sample offers a restricted seasonal comparison and
may have few Black records in parts of the design. A finite fitted
interval is not evidence that activity patterns, daylight overlap or
model controls are adequate. An undefined result reports a limitation of
the available design, not no disparity. In this sample, only 36 of 648
retained searches occur in darkness. The extremely wide adjusted
interval makes the point estimate unsuitable as a stable substantive
finding; more seasonal coverage and an explicit assessment of daylight
overlap are needed before interpreting this design.
cat(paste("-", attr(darkness, "assumptions")), sep = "\n")
#> - Darkness reduces visibility of ethnicity before the stop.
#> - Activity, exposure and police deployment are comparable after controls.
#> - Reporting does not vary differentially by darkness and ethnicity.
#> - Times, locations and self-defined ethnicity are measured adequately.
#> - Enough daylight/darkness overlap remains after controls.
#> - Repeated events and unmeasured selection can violate model independence.
#> - A vehicle-stop method needs justification for pedestrian searches.
The optional design = "dst" restricts observations to
three weeks on each side of the UK clock changes, adding transition and
running-day controls. Clock-change dates come from the Europe/London
time-zone rules, not fixed calendar dates. The tests verify 29 March and
25 October 2026 and the one-hour local-clock change. The bundled
May-July sample has no observations in these windows, so it cannot
support a DST comparison.
| NA |
0 |
insufficient outcome/predictor variation |
| input |
4657 |
| reliable force-month time |
4657 |
| valid location and instant |
4496 |
| known comparison pair |
2122 |
| annual intertwilight clock window |
747 |
| unambiguous daylight or darkness |
648 |
| design window |
0 |
Grogger and Ridgeway (2006; doi:10.1198/016214506000000168), listed in selected
publications by Ridgeway, developed the veil-of-darkness approach
for vehicle stops. Its use for pedestrian stop and search requires
separate justification: ethnicity may be visible before the decision
despite darkness, pedestrian and police activity can vary by light, and
selection or reporting can change. These observational diagnostics do
not establish a causal effect or, alone, establish discrimination.
Astronomical field names and the time-zone interface were verified
against the suncalc
package and manual.