The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
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.
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)
| ethnicity_5 | outcome | observed | missing_outcome | hit_rate | conf_low | conf_high |
|---|---|---|---|---|---|---|
| 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)
| outcome | odds_ratio | conf_low | conf_high | n | status |
|---|---|---|---|---|---|
| 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")])
| outcome | omitted_constant_controls |
|---|---|
| any_action | force_id |
| arrest | force_id |
| outcome_linked_to_object | force_id |
knitr::kable(attr(hits, "exclusions"))
| outcome | included | unknown_ethnicity | other_ethnicity | pair_missing_outcome_or_control |
|---|---|---|---|---|
| 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).
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.
knitr::kable(sl_timestamp_quality(records)[c("force_id", "month",
"gating_midnight_share", "time_reliable")], digits = 3)
| force_id | month | gating_midnight_share | time_reliable |
|---|---|---|---|
| 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")])
| odds_ratio | n | status |
|---|---|---|
| NA | 0 | insufficient outcome/predictor variation |
knitr::kable(attr(refused, "excluded_force_months"))
| force_id | month | time_reliable |
|---|---|---|
| west-yorkshire | 2026-05 | FALSE |
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.
darkness <- sl_veil_of_darkness(records)
knitr::kable(as.data.frame(darkness)[c("odds_ratio", "conf_low", "conf_high",
"n", "dark_stops", "daylight_stops", "status")], digits = 3)
| odds_ratio | conf_low | conf_high | n | dark_stops | daylight_stops | status |
|---|---|---|---|---|---|---|
| 593.664 | 0.295 | 1195173 | 648 | 36 | 612 | estimable |
knitr::kable(attr(darkness, "steps"))
| stage | remaining |
|---|---|
| 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"))
| force_id | month | time_reliable |
|---|
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.
dst <- sl_veil_of_darkness(records, design = "dst")
knitr::kable(as.data.frame(dst)[c("odds_ratio", "n", "status")])
| odds_ratio | n | status |
|---|---|---|
| NA | 0 | insufficient outcome/predictor variation |
knitr::kable(attr(dst, "steps"))
| stage | remaining |
|---|---|
| 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.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.