A survey that goes to the field has two costs that textbooks keep apart: the
statistical cost, the variance of the estimator for a given sample size, and
the field cost, the travel between units and the interviews, which is what the
budget actually pays for. fieldopt puts the two side by side. This vignette
walks through one design from the frame to the cost-variance frontier.
The frame is a data frame with one row per sampling unit (a segment, a farm, a census block) and its coordinates. One row is the depot, the office the teams leave from and return to. Here the frame is synthetic: forty segments in a 10 x 10 km square, with a crop area that trends from west to east.
library(fieldopt) library(dplyr, warn.conflicts = FALSE) set.seed(2026) frame <- tibble( unit = c("depot", paste0("s", 1:40)), x = c(5, runif(40, 0, 10)), y = c(5, runif(40, 0, 10)) ) |> mutate(crop = if_else(unit == "depot", NA_real_, 20 + 3 * x + rnorm(n(), sd = 2))) frame #> # A tibble: 41 × 4 #> unit x y crop #> <chr> <dbl> <dbl> <dbl> #> 1 depot 5 5 NA #> 2 s1 6.99 3.71 42.5 #> 3 s2 5.57 8.75 34.3 #> 4 s3 1.40 4.35 24.8 #> 5 s4 2.86 4.40 26.9 #> 6 s5 5.55 0.820 39.5 #> 7 s6 0.251 0.346 22.2 #> 8 s7 4.66 9.29 33.2 #> 9 s8 8.61 2.95 47.4 #> 10 s9 2.53 5.19 28.4 #> # ℹ 31 more rows
A real frame usually starts as a spatial object: as_frame() turns an sf
layer of segments or points into this data frame (centroids in latitude and
longitude, the area of each polygon), and as_sf() puts samples and results
back on the geometries for maps.
travel_matrix() computes the matrix of travel distances between all units
(or, given a sample and the depot as bases, only between the depot and
the sampled units, which is what the routing needs).
With longitude and latitude it uses great-circle distances in kilometres; with
planar coordinates, as here, Euclidean distances. A matrix computed on a road
network (for example with OSRM) can be passed through the matrix argument
and is the better input when it exists.
m <- travel_matrix(frame, method = "euclidean") m #> Travel matrix: 41 units, euclidean (distance); mean off-diagonal 4.886.
field_cost_model() turns a routing solution into money: a cost per unit of
travel, a fixed cost per visited unit (access, setting up) and a cost per
interview.
model <- field_cost_model( per_travel = 2, per_unit = 30, per_interview = 10, interviews_per_unit = 4, currency = "BRL" ) model #> Field cost model (BRL): 2 per travel unit, 0 per route, 30 per visited unit, 10 #> per interview.
select_units() draws a spatially balanced probability sample with the local
pivotal method: the inclusion probabilities are known, and units close to each
other are rarely selected together. Spatial balance usually lowers the
variance of totals of spatially structured variables; it also spreads the
units over the territory, which is exactly what makes the field work longer.
Making that tension explicit is the point of the package.
s <- frame |> filter(unit != "depot") |> select_units(n = 12) s |> sampled() |> select(unit, x, y, pi) #> # A tibble: 12 × 4 #> unit x y pi #> <chr> <dbl> <dbl> <dbl> #> 1 s3 1.40 4.35 0.3 #> 2 s6 0.251 0.346 0.3 #> 3 s8 8.61 2.95 0.3 #> 4 s11 0.0593 9.72 0.3 #> 5 s12 6.92 6.33 0.3 #> 6 s15 1.54 5.23 0.3 #> 7 s21 3.42 2.98 0.3 #> 8 s22 3.43 8.96 0.3 #> 9 s27 4.11 6.30 0.3 #> 10 s35 5.86 1.86 0.3 #> 11 s36 4.26 6.33 0.3 #> 12 s40 4.47 9.00 0.3
route_fieldwork() routes the field work from the depot through the selected
units. Without limits it returns one tour; with a maximum length or a maximum
number of stops per route, it splits the work into routes, one per team or per
day. Small instances are solved exactly; larger ones by a hybrid genetic
search with an optimal split of the tour and a local search, with a lower
bound and, for single tours, a proof of optimality, so that the quality of
the solution is visible. The sample can be piped straight into the routing.
r <- s |> travel_matrix(bases = frame |> filter(unit == "depot"), method = "euclidean") |> route_fieldwork(units = s, depot = "depot", max_stops = 5, cost_model = model) r #> #> ── Field routes ──────────────────────────────────────────────────────────────── #> 3 routes from "depot" through 12 units: total travel 42.62 distance (proven #> optimal). #> Route 1 (17.81): s3 > s15 > s11 > s22 > s40 #> Route 2 (21.56): s21 > s6 > s35 > s8 > s12 #> Route 3 (3.254): s36 > s27 #> Cost (BRL): travel 85.24 + routes 0 + units 360 + interviews 480 = 925.2. autoplot(r)

design_variance() gives the Horvitz-Thompson estimate of the total of crop
and its variance by the local-mean estimator of Grafström and Schelin (2014),
which suits spatially balanced samples and needs no joint inclusion
probabilities. The variance the same sample size would give under simple
random sampling is shown for comparison.
design_variance(s, y = "crop") #> # A tibble: 1 × 9 #> total variance se cv variance_srs n N variance_method df #> <dbl> <dbl> <dbl> <dbl> <dbl> <int> <int> <chr> <int> #> 1 1254. 3158. 56.2 0.0448 5874. 12 40 local-mean 11 sum(frame$crop, na.rm = TRUE) #> [1] 1241.484
A design is chosen by its sample size, its routing limits and its cost model.
cost_variance_frontier() repeats the select-route-price-estimate cycle for a
grid of sample sizes and returns the mean cost and the mean variance at each
size: the frontier a survey planner faces.
f <- cost_variance_frontier( frame, depot = "depot", cost_model = model, n_grid = c(6, 9, 12, 16, 20, 24), y = "crop", method = "euclidean", max_stops = 5, n_rep = 8, iterations = 60 ) f #> # A tibble: 6 × 9 #> n cost_mean cost_sd cost_per_unit travel_mean routes_mean variance_mean #> * <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> #> 1 6 475. 6.08 79.2 27.6 2 13594. #> 2 9 701. 4.64 77.9 35.5 2 4791. #> 3 12 925. 8.47 77.1 42.7 3 2958. #> 4 16 1218. 6.22 76.1 48.9 4 1529. #> 5 20 1517. 0.949 75.9 58.7 4 1136. #> 6 24 1810. 3.69 75.4 65.1 5 825. #> # ℹ 2 more variables: cv_mean <dbl>, n_rep <dbl> autoplot(f)

The frontier makes two questions answerable with numbers: how much the next
unit of precision costs in the field, and how much a different field
organisation (more teams, longer days, a closer depot) would change that
price. The vignettes routing-teams and frames-and-allocation take those
questions up.
Cochran, W. G. (1977). Sampling Techniques, third edition. Wiley.
Grafström, A., Lundström, N. L. P. and Schelin, L. (2012). Spatially balanced sampling through the pivotal method. Biometrics, 68(2), 514--520.
Grafström, A. and Schelin, L. (2014). How to select representative samples. Scandinavian Journal of Statistics, 41(2), 277--290.
Prins, C. (2004). A simple and effective evolutionary algorithm for the vehicle routing problem. Computers & Operations Research, 31(12), 1985--2002.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.