Skip to contents

Overview

This vignette documents the mrm_otis_*(), mrm_tps_*(), and mrm_siu_*() empirical callables. Each is a one-line entry point to one analysis. The OTIS and TPS examples run on the small reference samples returned by morie_sample(); the SIU examples run on the corpus bundled in rmoriedata. The vignette needs no network access.

For the full datasets:

  • OTIS -> morie_load_dataset("otisb01") (downloads from data.ontario.ca on first call, then reads the local cache)
  • TPS -> morie_fetch_tps("Assault") (ArcGIS REST)
  • SIU -> morie_fetch_siu() (the rmoriedata corpus by default; a live fetch with options(morie.siu.allow_fetch = TRUE))

See vignette("mrm-dataset-fetchers") for the dataset side.

library(rmorie)
b01 <- morie_sample("otis_b01")
b09 <- morie_sample("otis_b09")
tps <- morie_sample("tps_assault")

# Some events carry latitude and longitude 0, i.e. no usable
# location; drop them before any spatial analysis.
sum(tps$LAT_WGS84 == 0 | tps$LONG_WGS84 == 0)
#> [1] 21
tps <- tps[tps$LAT_WGS84 != 0 & tps$LONG_WGS84 != 0, ]

Left in, those points stretch the grid used by mrm_tps_moran_clustering() across the Atlantic, put almost every Toronto event in one cell, and add steps of thousands of kilometres to mrm_tps_levy_scaling().

OTIS suite

Placement-count concentration on b09

The b09 long-format file publishes per (fiscal year x placement-count band x gender) counts of individuals in segregation. The callable expands the banded counts using midpoints and returns Hill-MLE Pareto exponent, Gini coefficient, mean placements per individual, and the top-k% concentration share.

mrm_otis_placement_concentration(b09)
#>     year n_individuals n_placements mean_per_individual   gini hill_alpha
#> 1   2023         12647        55421            4.382146 0.5331     1.6890
#> 2   2024         10881        47123            4.330760 0.5862     1.7565
#> 3   2025          9608        46893            4.880620 0.6057     1.7230
#> 4 pooled         33136       149437            4.509808 0.5748     1.7198
#>   top_pct_share
#> 1        0.2932
#> 2        0.3351
#> 3        0.3215
#> 4        0.3180

The values are computed within fiscal year: OTIS UniqueIndividual_ID has format YYYY-XXXXX-SG and is randomly reassigned every fiscal year, so cross-year tracking is invalid by design.

Segregation-duration KM on b01

NumberConsecutiveDays_Segregation is the duration in days of each placement (no censoring – all durations are observed). The callable reports per stratum the mean and median duration, q75_days (the 75th percentile: the duration that a quarter of placements exceed), the percentage of placements above the UN Mandela 15-day cutoff, and the durations at which the empirical survival function falls to 50, 25, 10, 5 and 1 percent.

mrm_otis_seg_duration_km(b01)
#>   stratum    n mean_days median_days q75_days pct_above_mandela
#> 1  pooled 1000      3.03           2        3               0.7
#>   median_among_above_mandela days_at_S50 days_at_S25 days_at_S10 days_at_S05
#> 1                         54           2           3           5           7
#>   days_at_S01
#> 1       12.01
mrm_otis_seg_duration_km(b01, group_cols = "MentalHealth_Alert")
#>   stratum   n mean_days median_days q75_days pct_above_mandela
#> 1      No 499      2.76           2        3               0.6
#> 2     Yes 501      3.29           2        4               0.8
#>   median_among_above_mandela days_at_S50 days_at_S25 days_at_S10 days_at_S05
#> 1                       54.0           2           3           4           6
#> 2                       41.5           2           4           5           8
#>   days_at_S01
#> 1       11.02
#> 2       13.00

This callable replaces the misreading of YYYY-XXXXX-SG as a persistent person identifier, which produces a spurious cross-year “time-to-readmission” artifact.

Mortification co-occurrence (alert columns)

The three b01 alert flags (MentalHealth_Alert, SuicideRisk_Alert, SuicideWatch_Alert) co-occur to a degree well above independence. The substantive figure is MentalHealth x SuicideRisk Cramer’s V.

mrm_otis_mortification_cooccurrence(b01)
#>              alert_a            alert_b    n   chi2 df   p_value cramers_v
#> 1 MentalHealth_Alert  SuicideRisk_Alert 1000  33.25  1  8.12e-09    0.1823
#> 2 MentalHealth_Alert SuicideWatch_Alert 1000  12.09  1  5.08e-04    0.1099
#> 3  SuicideRisk_Alert SuicideWatch_Alert 1000 470.37  1 2.66e-104    0.6858

Region locality

mrm_otis_region_locality() cross-tabulates the region at the time of placement against the most recent region. The diagonal share is the fraction of placements whose region did not change:

res <- mrm_otis_region_locality(b01)
print(res$table)
#>           
#>            Central Eastern Northern Toronto Western
#>   Central      391       1        1       7      11
#>   Eastern        6     112        0       3       2
#>   Northern       0       0       92       0       2
#>   Toronto        3       2        1     155       2
#>   Western        6       2        1       2     198
cat("diagonal share:", res$diagonal_share, "  V:", res$cramers_v, "\n")
#> diagonal share: 0.948   V: 0.9348

Mandela classification

mrm_classify_mandela() supports three denominators:

mrm_classify_mandela(b01, denominator = "row")           # per-placement
#>     year denominator n_mandela        rate  pct n_broader_rc rate_broader
#> 1   2023         362         0 0.000000000 0.00            0  0.000000000
#> 2   2024         337         3 0.008902077 0.89            3  0.008902077
#> 3   2025         301         4 0.013289037 1.33            4  0.013289037
#> 4 pooled        1000         7 0.007000000 0.70            7  0.007000000
mrm_classify_mandela(b01, denominator = "individual_any") # per-person
#>     year denominator n_mandela        rate  pct n_broader_rc rate_broader
#> 1   2023         354         0 0.000000000 0.00            0  0.000000000
#> 2   2024         329         3 0.009118541 0.91            3  0.009118541
#> 3   2025         289         4 0.013840830 1.38            4  0.013840830
#> 4 pooled         972         7 0.007201646 0.72            7  0.007201646
mrm_classify_mandela(b01, denominator = "individual_cumulative")
#>     year denominator n_mandela        rate  pct n_broader_rc rate_broader
#> 1   2023         354         0 0.000000000 0.00            0  0.000000000
#> 2   2024         329         3 0.009118541 0.91            3  0.009118541
#> 3   2025         289         5 0.017301038 1.73            5  0.017301038
#> 4 pooled         972         8 0.008230453 0.82            8  0.008230453

The per-individual rates by fiscal year come from table c11, which morie_sample("otis_c11") returns whole. morie_otis_analyze_c11_mandela_classification() classifies the individuals in each year as at or below 15 days (solitary) or above (torture, in the function’s labels):

c11 <- morie_sample("otis_c11")
m11 <- morie_otis_analyze_c11_mandela_classification(c11)
rows <- lapply(m11$tables[[1]]$rows, function(r) unlist(lapply(r, as.character)))
tab11 <- as.data.frame(do.call(rbind, rows))
names(tab11) <- m11$tables[[1]]$headers
tab11[tab11$Type == "Segregation", ]
#>   Year        Type Solitary N Solitary % Torture N Torture % Total
#> 1 2023 Segregation      11071      87.5%      1576     12.5% 12647
#> 3 2024 Segregation       9084      83.5%      1797     16.5% 10881
#> 5 2025 Segregation       7626      79.4%      1982     20.6%  9608

TPS suite

Levy-flight Hill exponent on inter-event step lengths

Treats consecutive events in chronological order as a single stream and computes the haversine inter-event step length (km). Returns the Hill-MLE exponent restricted to steps above min_step_km.

mrm_tps_levy_scaling(tps)
#> $n_events
#> [1] 979
#> 
#> $n_dropped_coords
#> [1] 0
#> 
#> $n_steps_tail
#> [1] 974
#> 
#> $min_step_km
#> [1] 0.5
#> 
#> $hill_alpha
#> [1] 1.3336

Moran’s I + DBSCAN clustering

Grids the WGS84 extent into a coarse raster, counts events per cell, and computes the global Moran’s I via a rook-contiguity matrix. Also runs DBSCAN on the raw lat/long points (rescaled to km) for cluster counts.

mrm_tps_moran_clustering(tps, grid_resolution = 20L)
#> $morans_I
#> [1] 0.593565
#> 
#> $morans_z
#> [1] 17.08
#> 
#> $dbscan_n_clusters
#> [1] 20
#> 
#> $dbscan_n_noise
#> [1] 727
#> 
#> $dbscan_largest
#> [1] 82
#> 
#> $n_events
#> [1] 979
#> 
#> $n_dropped_coords
#> [1] 0

For the full Assault layer, read the file that morie_fetch_tps("Assault") writes (utils::read.csv() on the returned path), drop the zero coordinates, and pass the data frame.

Neighbourhood inter-event recurrence

For each HOOD_158 neighbourhood, sorts events chronologically and computes the gap (in days) between consecutive events.

head(mrm_tps_neighbourhood_recurrence_km(tps))
#>   hood n_events n_gaps mean_gap_days median_gap_days p25_gap_days p75_gap_days
#> 1  001       17     16        267.06           213.5       101.00       356.75
#> 2  002       12     11        447.91           360.0        74.00       606.00
#> 3  003        5      4        887.75           869.0       557.75      1199.00
#> 4  004        3      2       1781.00          1781.0      1065.50      2496.50
#> 5  005        4      3        776.00           676.0       537.00       965.00
#> 6  006        5      4        845.00           731.0       289.50      1286.50

Hawkes manifest loader

mrm_tps_load_hawkes_refit(manifest_path) reads a paper_hawkes_refit.json manifest of per-category Hawkes fits and returns it as a data frame. No manifest ships with the package, so manifest_path must point to one you have produced.

SIU suite

The SIU callables operate on the SIU.csv file that morie_fetch_siu() writes. By default it writes the panel-reviewed corpus bundled in rmoriedata (English and French reports); lang = "en" keeps the English rows.

siu_path <- morie_fetch_siu(lang = "en")
#> SIU: wrote the panel-reviewed corpus from rmoriedata (2309 rows) to /tmp/Rtmpm3faUk/morie/siu/SIU.csv. For a live re-fetch set options(morie.siu.allow_fetch = TRUE).
siu <- utils::read.csv(siu_path)
dim(siu)
#> [1] 2309   66
res <- mrm_siu_case_to_decision_km(siu)
print(res$pooled)
#>   stratum    n n_censored median_days mean_days p25_days p75_days max_days
#> 1  pooled 2295          5         121    237.44      118      280    12857
head(res$by_service[order(-res$by_service$n), ])
#>                            stratum   n n_censored median_days mean_days
#> 30          Toronto Police Service 499          2       121.0    282.44
#> 18       Ontario Provincial Police 447          1       121.0    203.18
#> 20            Peel Regional Police 199          2       121.0    257.47
#> 15 Niagara Regional Police Service 102          0       126.5    219.97
#> 19           Ottawa Police Service  98          0       120.5    207.72
#> 10         Hamilton Police Service  96          0       139.5    265.16
#>    p25_days p75_days max_days
#> 30      118      296    12857
#> 18      118      267     5944
#> 20      119      261     4998
#> 15      119      312      900
#> 19      118      303      857
#> 10      119      302     4552
head(mrm_siu_per_service_rate(siu))
#>                           service year n_cases
#> 1          Toronto Police Service 1982       1
#> 2  Durham Regional Police Service 2005       2
#> 3 Niagara Regional Police Service 2005       1
#> 4       Ontario Provincial Police 2005       1
#> 5       Owen Sound Police Service 2005       1
#> 6      Thunder Bay Police Service 2005       1

mrm_siu_case_to_decision_km() measures the days from incident to the Director’s decision; open cases are treated as censored. The max_days column shows that some incident dates in the corpus are decades before the decision, so check extreme rows before using the mean.

References

  • OTIS data dictionary – data.ontario.ca/dataset/data-on-inmates-in-ontario.
  • Toronto Police Open Data – data.torontopolice.on.ca/.
  • SIU public Director’s Reports – siu.on.ca/en/case_directors_reports.php.
  • Software: citation("rmorie").