splnr_apply_cutoffs() transforms numeric feature data in an sf dataframe
into binary (0 or 1) presence/absence values based on specified cutoffs.
It provides flexibility to either keep values above a cutoff as 1 (default)
or invert this logic to keep values below a cutoff as 1.
Arguments
- features
An
sfdataframe. It must contain ageometrycolumn and at least one numeric column to which cutoffs will be applied.- Cutoffs
One of:
A single unnamed numeric value in
[0, 1]— applied to all numeric columns.A single unnamed function that accepts a numeric vector and returns a single numeric in
[0, 1]— called independently per column with the non-NAvalues of that column.A named numeric vector — names must match numeric column names in
features; each value is applied to its named column only.A named list of numerics and/or functions — names must match numeric column names in
features; each entry is applied to its named column only.
- inverse
A logical value (
TRUEorFALSE). IfTRUE, values below the threshold are converted to1(and others to0). IfFALSE(default), values at or above the threshold are converted to1.
Value
A modified sf dataframe with the same structure and geometry as
features, but with all targeted numeric columns transformed into binary
(0 or 1) values based on the specified cutoffs and inverse setting.
Details
This function is crucial for standardizing feature data, such as species
probability distributions or habitat suitability scores, into a binary format
often required for conservation planning and spatial analysis (e.g., in
prioritizr).
The function operates in four modes based on the Cutoffs parameter:
Single numeric scalar: A single unnamed numeric value (e.g.,
0.5) is applied uniformly to all numeric columns infeatures, excluding thegeometrycolumn.Single function: A single unnamed function (e.g.,
\(x) quantile(x, 0.99)) is called independently for each numeric column, withxbeing the non-NAvalues of that column. The returned scalar is then used as the threshold for that column. Because the function is evaluated per-column, different columns may receive different thresholds even though the same function is supplied.Named numeric vector: A named numeric vector (e.g.,
c("feature1" = 0.5, "feature2" = 0.3)) applies each value to its corresponding named column only.Named list of numerics and/or functions: A named list (e.g.,
list("feature1" = 0.5, "feature2" = \(x) quantile(x, 0.99))) applies each entry to its corresponding named column. Numeric entries are used directly; function entries are called with the non-NAvalues of that column and must return a single numeric in[0, 1].
For all modes, the binarisation rules are:
If
value >= threshold, it becomes1.If
value < threshold, it becomes0.NAvalues are always converted to0.
The inverse parameter flips the result after binarisation:
inverse = FALSE(default): values at or above the threshold become1.inverse = TRUE: values below the threshold become1.
All resolved threshold values (whether supplied directly or returned by a
function) must lie in [0, 1]. NA values are stripped from the column
vector before it is passed to a function-based cutoff.
Examples
# Example 1: Single numeric cutoff applied to all numeric feature columns
df_single_cutoff <- splnr_apply_cutoffs(dat_species_prob, Cutoffs = 0.5)
#> Applying single cutoff of 0.5 to all numeric feature columns.
print(df_single_cutoff)
#> Simple feature collection with 780 features and 5 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 100 ymin: -50 xmax: 160 ymax: 2
#> Geodetic CRS: WGS 84
#> # A tibble: 780 × 6
#> geometry Spp1 Spp2 Spp3 Spp4 Spp5
#> <POLYGON [°]> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ((100 -50, 102 -50, 102 -48, 100 -48, 100 -50)) 1 0 0 0 0
#> 2 ((102 -50, 104 -50, 104 -48, 102 -48, 102 -50)) 1 0 1 1 0
#> 3 ((104 -50, 106 -50, 106 -48, 104 -48, 104 -50)) 0 1 0 1 1
#> 4 ((106 -50, 108 -50, 108 -48, 106 -48, 106 -50)) 1 1 1 1 1
#> 5 ((108 -50, 110 -50, 110 -48, 108 -48, 108 -50)) 0 1 0 1 1
#> 6 ((110 -50, 112 -50, 112 -48, 110 -48, 110 -50)) 1 0 1 0 0
#> 7 ((112 -50, 114 -50, 114 -48, 112 -48, 112 -50)) 0 1 0 0 0
#> 8 ((114 -50, 116 -50, 116 -48, 114 -48, 114 -50)) 0 0 0 0 1
#> 9 ((116 -50, 118 -50, 118 -48, 116 -48, 116 -50)) 0 1 1 0 1
#> 10 ((118 -50, 120 -50, 120 -48, 118 -48, 118 -50)) 1 0 1 0 1
#> # ℹ 770 more rows
# Example 2: Single function cutoff applied to all numeric feature columns
# Each column independently receives the 99th-percentile of its own values
# as its threshold.
df_fn_cutoff <- splnr_apply_cutoffs(
dat_species_prob,
Cutoffs = \(x) quantile(x, 0.99)
)
#> Applying function-based cutoff independently to each numeric feature column.
#> Column 'Spp1': resolved threshold = 0.985033159928862
#> Column 'Spp2': resolved threshold = 0.993628154252656
#> Column 'Spp3': resolved threshold = 0.992002531981561
#> Column 'Spp4': resolved threshold = 0.987106560785323
#> Column 'Spp5': resolved threshold = 0.986841972519178
print(df_fn_cutoff)
#> Simple feature collection with 780 features and 5 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 100 ymin: -50 xmax: 160 ymax: 2
#> Geodetic CRS: WGS 84
#> # A tibble: 780 × 6
#> geometry Spp1 Spp2 Spp3 Spp4 Spp5
#> <POLYGON [°]> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ((100 -50, 102 -50, 102 -48, 100 -48, 100 -50)) 0 0 0 0 0
#> 2 ((102 -50, 104 -50, 104 -48, 102 -48, 102 -50)) 0 0 0 0 0
#> 3 ((104 -50, 106 -50, 106 -48, 104 -48, 104 -50)) 0 0 0 0 0
#> 4 ((106 -50, 108 -50, 108 -48, 106 -48, 106 -50)) 0 0 0 0 0
#> 5 ((108 -50, 110 -50, 110 -48, 108 -48, 108 -50)) 0 0 0 0 0
#> 6 ((110 -50, 112 -50, 112 -48, 110 -48, 110 -50)) 0 0 0 0 0
#> 7 ((112 -50, 114 -50, 114 -48, 112 -48, 112 -50)) 0 0 0 0 0
#> 8 ((114 -50, 116 -50, 116 -48, 114 -48, 114 -50)) 0 0 0 0 0
#> 9 ((116 -50, 118 -50, 118 -48, 116 -48, 116 -50)) 0 0 0 0 0
#> 10 ((118 -50, 120 -50, 120 -48, 118 -48, 118 -50)) 0 0 0 0 0
#> # ℹ 770 more rows
# Example 3: Named numeric cutoffs for specific columns
df_named_cutoffs <- splnr_apply_cutoffs(
dat_species_prob,
Cutoffs = c("Spp1" = 0.6, "Spp2" = 0.4)
)
#> Applying named cutoffs to specific feature columns.
#> Column 'Spp1': resolved threshold = 0.6
#> Column 'Spp2': resolved threshold = 0.4
print(df_named_cutoffs)
#> Simple feature collection with 780 features and 5 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 100 ymin: -50 xmax: 160 ymax: 2
#> Geodetic CRS: WGS 84
#> # A tibble: 780 × 6
#> geometry Spp1 Spp2 Spp3 Spp4 Spp5
#> <POLYGON [°]> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ((100 -50, 102 -50, 102 -48, 100 -48, 100… 1 0 0.0969 0.435 0.0418
#> 2 ((102 -50, 104 -50, 104 -48, 102 -48, 102… 0 1 0.504 0.503 0.360
#> 3 ((104 -50, 106 -50, 106 -48, 104 -48, 104… 0 1 0.285 0.755 0.653
#> 4 ((106 -50, 108 -50, 108 -48, 106 -48, 106… 0 1 0.564 0.503 0.529
#> 5 ((108 -50, 110 -50, 110 -48, 108 -48, 108… 0 1 0.150 0.863 0.753
#> 6 ((110 -50, 112 -50, 112 -48, 110 -48, 110… 1 1 0.807 0.458 0.374
#> 7 ((112 -50, 114 -50, 114 -48, 112 -48, 112… 0 1 0.00963 0.102 0.114
#> 8 ((114 -50, 116 -50, 116 -48, 114 -48, 114… 0 0 0.481 0.231 0.764
#> 9 ((116 -50, 118 -50, 118 -48, 116 -48, 116… 0 1 0.552 0.00978 0.552
#> 10 ((118 -50, 120 -50, 120 -48, 118 -48, 118… 1 1 0.695 0.00687 0.815
#> # ℹ 770 more rows
# Example 4: Named list mixing fixed and function-based cutoffs
df_mixed_cutoffs <- splnr_apply_cutoffs(
dat_species_prob,
Cutoffs = list(
"Spp1" = 0.5,
"Spp2" = \(x) quantile(x, 0.99)
)
)
#> Applying named cutoffs to specific feature columns.
#> Column 'Spp1': resolved threshold = 0.5
#> Column 'Spp2': resolved threshold = 0.993628154252656
print(df_mixed_cutoffs)
#> Simple feature collection with 780 features and 5 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 100 ymin: -50 xmax: 160 ymax: 2
#> Geodetic CRS: WGS 84
#> # A tibble: 780 × 6
#> geometry Spp1 Spp2 Spp3 Spp4 Spp5
#> <POLYGON [°]> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ((100 -50, 102 -50, 102 -48, 100 -48, 100… 1 0 0.0969 0.435 0.0418
#> 2 ((102 -50, 104 -50, 104 -48, 102 -48, 102… 1 0 0.504 0.503 0.360
#> 3 ((104 -50, 106 -50, 106 -48, 104 -48, 104… 0 0 0.285 0.755 0.653
#> 4 ((106 -50, 108 -50, 108 -48, 106 -48, 106… 1 0 0.564 0.503 0.529
#> 5 ((108 -50, 110 -50, 110 -48, 108 -48, 108… 0 0 0.150 0.863 0.753
#> 6 ((110 -50, 112 -50, 112 -48, 110 -48, 110… 1 0 0.807 0.458 0.374
#> 7 ((112 -50, 114 -50, 114 -48, 112 -48, 112… 0 0 0.00963 0.102 0.114
#> 8 ((114 -50, 116 -50, 116 -48, 114 -48, 114… 0 0 0.481 0.231 0.764
#> 9 ((116 -50, 118 -50, 118 -48, 116 -48, 116… 0 0 0.552 0.00978 0.552
#> 10 ((118 -50, 120 -50, 120 -48, 118 -48, 118… 1 0 0.695 0.00687 0.815
#> # ℹ 770 more rows
# Example 5: Single numeric cutoff with inverse logic
df_inverse_cutoff <- splnr_apply_cutoffs(dat_species_prob, Cutoffs = 0.5, inverse = TRUE)
#> Applying single cutoff of 0.5 to all numeric feature columns.
#> Inverse logic applied: values below threshold will be 1.
print(df_inverse_cutoff)
#> Simple feature collection with 780 features and 5 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 100 ymin: -50 xmax: 160 ymax: 2
#> Geodetic CRS: WGS 84
#> # A tibble: 780 × 6
#> geometry Spp1 Spp2 Spp3 Spp4 Spp5
#> <POLYGON [°]> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ((100 -50, 102 -50, 102 -48, 100 -48, 100 -50)) 0 1 1 1 1
#> 2 ((102 -50, 104 -50, 104 -48, 102 -48, 102 -50)) 0 1 0 0 1
#> 3 ((104 -50, 106 -50, 106 -48, 104 -48, 104 -50)) 1 0 1 0 0
#> 4 ((106 -50, 108 -50, 108 -48, 106 -48, 106 -50)) 0 0 0 0 0
#> 5 ((108 -50, 110 -50, 110 -48, 108 -48, 108 -50)) 1 0 1 0 0
#> 6 ((110 -50, 112 -50, 112 -48, 110 -48, 110 -50)) 0 1 0 1 1
#> 7 ((112 -50, 114 -50, 114 -48, 112 -48, 112 -50)) 1 0 1 1 1
#> 8 ((114 -50, 116 -50, 116 -48, 114 -48, 114 -50)) 1 1 1 1 0
#> 9 ((116 -50, 118 -50, 118 -48, 116 -48, 116 -50)) 1 0 0 1 0
#> 10 ((118 -50, 120 -50, 120 -48, 118 -48, 118 -50)) 0 1 0 1 0
#> # ℹ 770 more rows
