Where fieldopt applies: beyond agricultural surveys
Source:vignettes/applications.Rmd
applications.RmdThe package was built for agricultural surveys with an area frame, but its pieces are general: a probability sample of places, the cost of visiting them, and the estimate that comes out. Any survey that sends people to locations fits. Four settings, each with the functions it uses.
| Setting | Units | Balanced on | Field constraint | Main functions |
|---|---|---|---|---|
| Agricultural area frame | grid cells, points | space, intensity | team-days, service per cell | all (plan vignette) |
| Environmental monitoring | sites (lakes, plots, stations) | space, covariates | daily range of boats or vehicles |
select_units(method = "cube"),
route_fieldwork()
|
| Household survey | enumeration areas, then households | population size | interviewer-days, several offices |
two_stage_allocation(),
schedule_fieldwork(region = )
|
| Establishment survey | listed firms plus area segments | coverage of the unlisted | screening in the field |
frame_overlap(), dual_frame_allocation(),
dual_frame_estimator()
|
Environmental monitoring: sites balanced on covariates
Three hundred candidate lakes with altitude and surface; the monitoring programme can visit forty a season with two boats, each out for at most ten hours a day including four hours of sampling per lake. The cube method balances the sample on the covariates while the routes keep the days feasible.
set.seed(31)
lakes <- tibble(
unit = paste0("lake", 1:300),
x = runif(300, 0, 200), y = runif(300, 0, 120)
) |>
mutate(altitude = 200 + 8 * y + rnorm(n(), sd = 40), surface = exp(rnorm(n(), 2, 0.8)))
s <- lakes |>
select_units(n = 40, method = "cube", balance = c("altitude", "surface"), seed = 2)
bind_rows(frame = lakes, sample = sampled(s), .id = "set") |>
group_by(set) |>
summarise(across(c(altitude, surface), mean))
#> # A tibble: 2 × 3
#> set altitude surface
#> <chr> <dbl> <dbl>
#> 1 frame 685. 10.2
#> 2 sample 674. 10.3
harbour <- tibble(unit = "harbour", x = 100, y = 0)
r <- s |>
travel_matrix(bases = harbour, method = "euclidean", speed = 60) |> # 60 km/h: minutes
route_fieldwork(
units = s, depot = "harbour",
max_length = 600, service_time = 240, iterations = 100
)
tibble(days = r$n_routes, longest_day = round(max(r$durations)))
#> # A tibble: 1 × 2
#> days longest_day
#> <int> <dbl>
#> 1 36 598Household survey: enumeration areas from several offices
Enumeration areas are selected with probability proportional to their households, then a fixed number of households inside each; the two-stage allocation says how many of each, and the schedule assigns the areas to the regional offices and their interviewers.
set.seed(5)
ea <- tibble(unit = paste0("ea", 1:400), x = runif(400, 0, 60), y = runif(400, 0, 40)) |>
mutate(households = round(exp(rnorm(n(), 5, 0.4))))
two_stage_allocation(
n_primary = 400, m_secondary = mean(ea$households), s2_between = 0.04, s2_within = 0.22,
c1 = 350, c2 = 18, target_cv = 0.03, mean = 0.4
)
#>
#> ── Two-stage allocation ────────────────────────────────────────────────────────
#> n = 249 primary units with m = 10 secondary units each (optimal m 10.52): cost
#> 132000, variance of the total 621600 (SE 788.4, CV 3%).
offices <- tibble(unit = c("office_w", "office_e"), x = c(12, 48), y = c(20, 20))
s <- ea |> select_units(n = 36, size = "households", seed = 9)
sch <- s |>
travel_matrix(bases = offices, method = "euclidean", speed = 20) |> # 20 km/h: minutes
schedule_fieldwork(
units = s, teams = c(office_w = 3, office_e = 2), days = 10,
max_length = 480, service_time = 300, iterations = 60
)
sch$summary
#> # A tibble: 2 × 7
#> base units teams routes days_needed days_available fits
#> * <chr> <int> <dbl> <int> <int> <dbl> <lgl>
#> 1 office_w 18 3 18 6 10 TRUE
#> 2 office_e 18 2 18 9 10 TRUEEstablishment survey: a list and an area frame
A register covers the large establishments but misses the small and the new ones; an area frame covers everything but is expensive. The dual-frame design samples both, sizes them for the money available, and the estimator combines the two samples. The dual-frame vignette shows the estimation; the allocation below spends a budget between the frames.
domains <- tibble(
domain = c("a", "ab", "b"), size = c(5000, 800, 150),
mean = c(12, 90, 110), sd = c(10, 60, 70)
)
dual_frame_allocation(domains, cost_a = 220, cost_b = 35, deff_a = 1.6, budget = 150000)
#>
#> ── Dual-frame allocation ───────────────────────────────────────────────────────
#> Minimum variance for budget 150000: n_A = 530 (frame A, cost 220/unit), n_B =
#> 950 (frame B, cost 35/unit), theta = 0.088 (optimised).
#> Cost 149800; variance 8492000 (frame A 8490000, frame B 0); CV 1.96% of the
#> total 148500.
#> Expected overlap units: 73.1 in the A sample, 800 in the B sample; a bound on a
#> sample size was active.What stays the same across settings
Every setting goes through the same objects: a frame with
coordinates, a fieldopt_sample with known inclusion
probabilities, a travel matrix, a fieldopt_routes or
fieldopt_schedule, and a one-row tibble with the estimate
and its variance. The cost side (field_cost_model(),
cost_variance_frontier()) and the connectors to
survey, sf, OSRM, vrpr and
highs are the same too.