Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ message: 'To cite package "wowi" in publications use:'
type: software
license: GPL-3.0-or-later
title: 'wowi: Detect Spatial Clusters of High Acute Malnutrition Rates'
version: 1.0.1
version: 1.0.2
abstract: Utilities for detecting statistically significant spatial clusters of high
acute malnutrition rates using SaTScan's Bernoulli spatial-scan model.
authors:
Expand All @@ -23,7 +23,7 @@ preferred-citation:
authors:
- name: Tomás Zaba
year: '2025'
notes: R package version 1.0.1
notes: R package version 1.0.2
url: https://tiwowi.github.io/wowi/
repository-code: https://github.com/tiwowi/wowi
url: https://tiwowi.github.io/wowi/
Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: wowi
Title: Detect Spatial Clusters of High Acute Malnutrition Rates
Version: 1.0.1
Version: 1.0.2
Authors@R:
person(given = "Tomás",
family = "Zaba",
Expand Down
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# wowi 1.0.2

## Bug fixes

* Resolved issue [#61](https://github.com/tiwowi/wowi/issues/61).
In the previous version, the regular expressions used to match text patterns into tables would break whenever the list of location IDs in a cluster wrapped onto a new line, altering the expected match order. This caused several columns to remain empty. The parser now handles these cases programmatically.

# wowi 1.0.1

## General updates
Expand Down
3 changes: 2 additions & 1 deletion R/satscan-runner.R
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@
#'
#'

# no# nocov start
# nocov start

ww_run_satscan <- function(
.data,
filename = NULL,
Expand Down
111 changes: 72 additions & 39 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,91 @@
#'
#' @keywords internal
#'
#'
skip_if_no_satscan <- function(ss_path = "/Applications/SaTScan.app/Contents/app/satscan") {
testthat::skip_if_not(file.exists(ss_path), message = "SaTScan is not installed or not found")
}

#'
#'
#' Extract results from SaTScan-text-based output
#'
#' @param file SaTScan-text-based output result given as "main" to be parsed
#'
#' @keywords internal
#'
#'
#'
#'


# nocov start

parse_clusters <- function(file) {
## Access the txt-based results ----

## Subset SaTScan-text-based output file ----
txt <- file$main

## Find line indices with "Location IDs included" ----
## Find line indices for cluster starts and coordinates
cluster_start <- stringr::str_which(txt, "^\\d+\\.Location IDs included\\.:")
coords <- stringr::str_which(txt, "^\\s+Coordinates / radius")

## Extract the name of the survey area ----
area_name <- stringr::str_which(txt, "^[Case File]+\\:")

## Define line offsets to grab per cluster ----
cluster_blocks <- lapply(cluster_start, function(start_idx) {
txt[start_idx:(start_idx + 10)]
})

## Parse each cluster block ----
parsed_clusters <- lapply(cluster_blocks, function(block) {
tibble::tibble(
survey_area = stringr::str_extract_all(basename(txt[[area_name]]), "^[^.]+") |>
as.character(),
nr_EAs = stringr::str_extract(txt[[18]], "\\d+") |> as.integer(),
total_children = stringr::str_extract(txt[[19]], "\\d+") |> as.integer(),
total_cases = stringr::str_extract(txt[[20]], "\\d+") |> as.integer(),
`%_cases` = stringr::str_extract(txt[[21]], "\\d+") |> as.double(),
location_ids = stringr::str_extract_all(block[1], "[0-9]+")[[1]][-1] |>
paste(collapse = ","),
geo = stringr::str_extract(block[2], "\\d+\\.\\d+\\s+\\w\\,\\s+\\d+\\.\\d+\\s+\\w"),
radius = stringr::str_extract(block[2], "\\d+\\.\\d+\\s*\\w+$"),
span = stringr::str_extract(block[3], "[0-9]+[.]+[0-9]+\\s+\\w+$"),
children = stringr::str_extract(block[4], "[0-9]+") |> as.integer(),
n_cases = stringr::str_extract(block[5], "[0-9]+") |> as.integer(),
expected_cases = stringr::str_extract(block[6], "[0-9]+[.]+[0-9]+") |> as.double(),
observedExpected = stringr::str_extract(block[7], "[0-9]+[.]+[0-9]+") |> as.double(),
relative_risk = stringr::str_extract(block[8], "[0-9]+\\.\\d+") |> as.double(),
`%_cases_in_area` = stringr::str_extract(block[9], "[0-9]+\\.\\d+") |> as.double(),
log_lik_ratio = stringr::str_extract(block[10], "[0-9]+\\.\\d+") |> as.double(),
pvalue = stringr::str_extract(block[11], "[0-9]+[.]+[0-9]+") |> as.double(),
## Guard: align pairs if counts differ
n <- min(length(cluster_start), length(coords))
if (n == 0) return(tibble::tibble())

out <- vector("list", n)

for (j in seq_len(n)) {
idx <- cluster_start[j]
coord <- coords[j]

## Collect all lines containing IDs up to the coordinates line
id_block <- txt[idx:(coord - 1)]
ids_vec <- stringr::str_extract_all(id_block, "[0-9]+") |> unlist()

## Drop the leading cluster number (e.g., "1", "2") if present
location_ids <- if (length(ids_vec) > 1) {
paste(ids_vec[-1], collapse = ",")
} else {
NA_character_
}

## Build tibble for this cluster (offsets are stable in SaTScan output)
out[[j]] <- tibble::tibble(

### Summary metadata (from fixed lines in your example) ----
survey_area = as.character(stringr::str_extract_all(basename(txt[[area_name]]), "^[^.]+")),
nr_EAs = as.integer(stringr::str_extract(txt[18], "\\d+")),
total_children = as.integer(stringr::str_extract(txt[19], "\\d+")),
total_cases = as.integer(stringr::str_extract(txt[20], "\\d+")),
`%_cases` = as.double(stringr::str_extract(txt[21], "\\d+\\.?\\d*")),

### Cluster-specific ----
location_ids = location_ids,
geo = stringr::str_extract(txt[coord],
"\\d+\\.\\d+\\s+\\w\\,\\s+\\d+\\.\\d+\\s+\\w"),
radius = stringr::str_extract(txt[coord],"[0-9]+\\.[0-9]+\\s*km"),
span = stringr::str_extract(txt[coord + 1], "[0-9]+\\.[0-9]+\\s*km"),
children = as.integer(stringr::str_extract(txt[coord + 2], "\\d+")),
n_cases = as.integer(stringr::str_extract(txt[coord + 3], "\\d+")),
expected_cases = as.double(stringr::str_extract(txt[coord + 4], "[0-9]+\\.[0-9]+")),
observedExpected = as.double(stringr::str_extract(txt[coord + 5], "[0-9]+\\.[0-9]+")),
relative_risk = as.double(stringr::str_extract(txt[coord + 6], "[0-9]+\\.[0-9]+")),
`%_cases_in_area` = as.double(stringr::str_extract(txt[coord + 7], "[0-9]+\\.?[0-9]*")),
log_lik_ratio = as.double(stringr::str_extract(txt[coord + 8], "[0-9]+\\.[0-9]+")),
pvalue = as.double(stringr::str_extract(txt[coord + 9], "[0-9]+\\.?[0-9]*")),

### Check if IPC AMN reqs for survey disaggregation is met ----
ipc_amn = ifelse(
test = length(strsplit(.data$location_ids, ",\\s*")[[1]]) >= 5 & as.numeric(.data$children) > 100,
yes = "yes",
no = "no"
length(strsplit(location_ids, ",\\s*")[[1]]) >= 5 & !is.na(children) & children >= 100,
"yes", "no"
)
)
})
)
}

## Combine all into one data frame ----
dplyr::bind_rows(parsed_clusters)
## Return binded results ----
dplyr::bind_rows(out)
}

# nocov end
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostat
[![Lifecycle:
experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
[![R-CMD-check](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml)
[![Test
app](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml)
[![Codecov test
coverage](https://codecov.io/gh/tiwowi/wowi/graph/badge.svg)](https://app.codecov.io/gh/tiwowi/wowi)
<!-- badges: end -->
Expand Down Expand Up @@ -107,7 +109,7 @@ citation("wowi")

Tomás Zaba (2025). _wowi: Utilities for detecting statistically
significant spatial clusters of high acute malnutrition rates using
SaTScan's Bernoulli spatial-scan model_. R package version 1.0.1,
SaTScan's Bernoulli spatial-scan model_. R package version 1.0.2,
<https://tiwowi.github.io/wowi/>.

A BibTeX entry for LaTeX users is
Expand All @@ -116,7 +118,7 @@ citation("wowi")
title = {wowi: Utilities for detecting statistically significant spatial clusters of high acute malnutrition rates using SaTScan's Bernoulli spatial-scan model},
author = {{Tomás Zaba}},
year = {2025},
note = {R package version 1.0.1},
note = {R package version 1.0.2},
url = {https://tiwowi.github.io/wowi/},
}

Expand Down
1 change: 1 addition & 0 deletions README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ format: gfm
[![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)
[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
[![R-CMD-check](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml)
[![Test app](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tiwowi/wowi/actions/workflows/R-CMD-check.yaml)
[![Codecov test coverage](https://codecov.io/gh/tiwowi/wowi/graph/badge.svg)](https://app.codecov.io/gh/tiwowi/wowi)
<!-- badges: end -->

Expand Down
2 changes: 1 addition & 1 deletion inst/CITATION
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ bibentry(
title = "wowi: Utilities for detecting statistically significant spatial clusters of high acute malnutrition rates using SaTScan's Bernoulli spatial-scan model",
author = person("Tomás Zaba"),
year = 2025,
note = "R package version 1.0.1",
note = "R package version 1.0.2",
url = "https://tiwowi.github.io/wowi/"
)
2 changes: 1 addition & 1 deletion inst/app/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ui <- tagList(
),

### Right side of the page navigation bar ----
tags$span("v1.0.1",
tags$span("v1.0.2",
id = "app-version",
style = "font-size: 12.5px; color: rgba(31, 42, 68, 0.58);
position: fixed; top: 40px; right: 20px;"
Expand Down
15 changes: 15 additions & 0 deletions man/parse_clusters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions tests/testthat/test-utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ testthat::test_that(
.by = "zscores",
oedema = oedema
)


### Create a temporary directory ----
tmp <- withr::local_tempdir() # ensures cleanup after test
Expand Down Expand Up @@ -80,5 +81,23 @@ testthat::test_that(
testthat::expect_true(is.double(dplyr::pull(r[[1]][16])))
testthat::expect_true(is.double(dplyr::pull(r[[1]][17])))
testthat::expect_true(is.character(dplyr::pull(r[[1]][18])))

# ## Check if results are in the tibble are correct ----
df <- r$.df
testthat::expect_equal(df$nr_EAs[1], 36)
testthat::expect_equal(df$total_cases[1], 26)
testthat::expect_equal(df$"%_cases"[1], 7.8)
testthat::expect_equal(df$location_ids[1], "10,9")
testthat::expect_equal(df$geo[1], "34.113909 N, 3.087933 E")
testthat::expect_equal(df$radius[1], "1.20 km")
testthat::expect_equal(df$span[1], "1.20 km")
testthat::expect_equal(df$children[1], 25)
testthat::expect_equal(df$n_cases[1], 6)
testthat::expect_equal(df$expected_cases[1], 1.95)
testthat::expect_equal(df$observedExpected[1], 3.07)
testthat::expect_equal(df$relative_risk[1], 3.70)
testthat::expect_equal(df$"%_cases_in_area"[1], 24.0)
testthat::expect_equal(df$log_lik_ratio[1], 3.458213)
testthat::expect_equal(df$pvalue[1], 0.55)
}
)
Loading