Modeling with ethnobotanyR

Cory Whitney

ethnobotanyR logo

Please remember to cite the ethnobotanyR package if you use it in your publications. Use citation("ethnobotanyR") to get a citation for the latest version.

To cite package 'ethnobotanyR' in publications use:

  Whitney C (2026). _ethnobotanyR: Ethnobotanical Analysis,
  Decision-Framing, and TEK Modeling_. R package version 0.3.0,
  <https://CRAN.R-project.org/package=ethnobotanyR>.

A BibTeX entry for LaTeX users is

  @Manual{,
    title = {ethnobotanyR: Ethnobotanical Analysis, Decision-Framing, and TEK Modeling},
    author = {Cory Whitney},
    year = {2026},
    note = {R package version 0.3.0},
    url = {https://CRAN.R-project.org/package=ethnobotanyR},
  }

Data

We use homegardens, included in the package: plants and their uses in 102 homegardens in southwestern Uganda. Each garden is an informant. The data are described in Whitney et al. (2017) and analyzed in Whitney et al. (2018). Please cite both papers when you use these data.

Rows are one species in one garden. Columns after informant and sp_name are the 14 use categories, 0 = not used, 1 = used (same format as ethnobotanydata). Gardens only have rows for species they grow, so n differs by species.

First six rows of the homegardens data (first six use categories shown)
informant sp_name food sale medicine technical ornament fence
BUR1 Artocarpus heterophyllus 1 0 1 0 0 0
BUR1 Axonopus compressus 0 0 0 1 0 0
BUR1 Basella alba 1 0 0 0 0 0
BUR1 Canna indica 0 0 0 0 1 0
BUR1 Carica papaya 1 0 0 0 0 0
BUR1 Coffea canephora 0 1 0 0 0 0
dim(homegardens)
#> [1] 2870   16
nlevels(homegardens$informant)  # gardens
#> [1] 102
nlevels(homegardens$sp_name)    # species
#> [1] 225

homegardens_info has garden covariates and homegardens_species has life form, family and native status.

Modeling functions

Before modeling, be clear about the aims and objectives, and ground the work in the existing literature. The functions here move from description to uncertainty and shared knowledge:

Question Function
How likely is a use, with uncertainty? ethno_beta()
Is the sample large enough? ethno_saturation()
Which uses are in the consensus, and who knows most? ethno_consensus()
Consensus with competence you set ethno_bayes_consensus()
Uncertainty for any statistic ethno_boot()

Beta-binomial probability of use

ethno_beta() is the default for the probability that a garden growing a species uses it for a purpose. It works for small samples and rare uses.

beta_hg <- ethno_beta(homegardens, level = 0.9)
# species grown in at least 50 gardens with the highest use probabilities
beta_hg[beta_hg$n >= 50, ] %>%
  arrange(desc(mean)) %>%
  head(8)
#>                    sp_name  use  k  n      mean     lower     upper
#> 1 Xanthosoma sagittifolium food 88 88 0.9888889 0.9669003 0.9994238
#> 2         Musa (AAA Group) food 83 83 0.9882353 0.9649650 0.9993896
#> 3          Musa (AB Group) food 82 82 0.9880952 0.9645504 0.9993822
#> 4         Musa (AAB Group) food 74 74 0.9868421 0.9608441 0.9993163
#> 5    Saccharum officinarum food 70 70 0.9861111 0.9586843 0.9992778
#> 6        Manihot esculenta food 68 68 0.9857143 0.9575126 0.9992569
#> 7       Phaseolus vulgaris food 59 59 0.9836066 0.9512971 0.9991455
#> 8           Cucurbita pepo food 56 56 0.9827586 0.9488005 0.9991005

Intervals are wide when n is small. A species grown in 20 gardens cannot support a precise use probability.

coffee <- beta_hg[beta_hg$sp_name == "Coffea canephora" & beta_hg$k > 0, ]
ggplot(coffee, aes(x = mean, y = reorder(use, mean))) +
  geom_pointrange(aes(xmin = lower, xmax = upper)) +
  labs(x = "Share of gardens using Coffea canephora (90% interval)", y = "")

The non-parametric Bayesian bootstrap

ethno_boot() resamples the informants to show how much a statistic could change with different informants. It works for any statistic. Here, how often are avocado (Persea americana) and common bean (Phaseolus vulgaris) sold?

avocado <- homegardens$sale[homegardens$sp_name == "Persea americana"]
bean <- homegardens$sale[homegardens$sp_name == "Phaseolus vulgaris"]

set.seed(1)
avocado_boot <- ethno_boot(avocado, statistic = mean, n1 = 1000)
bean_boot <- ethno_boot(bean, statistic = mean, n1 = 1000)

quantile(avocado_boot, c(0.05, 0.95))
#>        5%       95% 
#> 0.1318681 0.3296703
quantile(bean_boot, c(0.05, 0.95))
#>        5%       95% 
#> 0.1864407 0.4745763

Compare the species with the difference of the paired draws, not by eye:

quantile(bean_boot - avocado_boot, c(0.05, 0.95))
#>          5%         95% 
#> -0.08237102  0.27013410
mean(bean_boot > avocado_boot)  # probability beans are sold more often
#> [1] 0.814
boot_long <- data.frame(species = rep(c("Persea americana", "Phaseolus vulgaris"), each = 1000),
                        value = c(avocado_boot, bean_boot))
ggplot(boot_long, aes(x = value, y = species, fill = species)) +
  ggridges::geom_density_ridges() +
  ggridges::theme_ridges() +
  theme(legend.position = "none") +
  labs(y = "", x = "Bayesian bootstrap of the share of gardens selling the plant")
#> Picking joint bandwidth of 0.0155

The bootstrap only reweights observed values. If no garden reports a use, every draw is zero and the interval has zero width, which is not credible for rare uses. ethno_boot() warns in this case; use ethno_beta() instead.

no_use <- homegardens$hygiene[homegardens$sp_name == "Persea americana"]
sum(no_use)  # no garden reports this use
#> [1] 0
quantile(ethno_boot(no_use, mean), c(0.05, 0.95))
#> Warning in ethno_boot(no_use, mean): All observations are identical, so the
#> bootstrap has no variation (zero-width interval). Consider a Beta-binomial
#> posterior for rare uses.
#>  5% 95% 
#>   0   0
persea <- ethno_beta(homegardens[homegardens$sp_name == "Persea americana", ])
persea[persea$use == "hygiene", ]
#>             sp_name     use k  n       mean        lower      upper
#> 10 Persea americana hygiene 0 91 0.01075269 0.0005573804 0.03203786

Is the sample large enough?

ethno_saturation() shows how many distinct species-use citations each added garden finds. A curve still rising means more gardens would add new species and uses.

sat <- ethno_saturation(homegardens, n_perm = 100)
ggplot(sat, aes(n_informants, mean)) +
  geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.3) +
  geom_line() +
  labs(x = "Gardens", y = "Distinct species-use pairs")

The curve is still rising at 102 gardens: new species-use combinations keep appearing.

Consensus with estimated competence

ethno_consensus() estimates each garden’s competence from agreement with other gardens across species-use questions, and returns the probability each species-use is in the consensus answer key. It assumes one shared answer key, so minority knowledge counts as error, and it needs enough shared questions. Here we keep the 36 species grown in at least 30 gardens. Gardens that do not grow a species have no answer for it (missing, not zero).

common <- names(which(table(homegardens$sp_name) >= 30))
hg_common <- droplevels(homegardens[homegardens$sp_name %in% common, ])
cons <- ethno_consensus(hg_common)
cons$converged
#> [1] TRUE
summary(cons$competence$D)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>  0.8041  0.9291  0.9473  0.9445  0.9736  0.9941
head(cons$truth[order(-cons$truth$p_used), ], 8)
#>                     sp_name   use p_used
#> 1  Artocarpus heterophyllus  food      1
#> 15            Carica papaya  food      1
#> 30         Coffea canephora  sale      1
#> 43           Cucurbita pepo  food      1
#> 57        Draceana fragrans  food      1
#> 62        Draceana fragrans fence      1
#> 71          Ipomoea batatas  food      1
#> 85        Manihot esculenta  food      1

Read the probabilities as ranks, not calibrated certainty: they become extreme as informants are added. Check whether low-competence gardens are a different knowledge group (for example by district) before treating them as error.

comp <- merge(cons$competence, homegardens_info, by = "informant")
ggplot(comp, aes(district, D)) +
  geom_boxplot() +
  labs(x = "", y = "Estimated competence (D)")

Consensus with fixed competence

ethno_bayes_consensus() takes competence from you, one value per row. Here we use the estimates from ethno_consensus() for the gardens growing one species. This is the Bayesian consensus for a single species (Oravecz et al. (2014); Romney et al. (1986)).

coffee_data <- hg_common[hg_common$sp_name == "Coffea canephora", ]
coffee_D <- cons$competence$D[match(coffee_data$informant, cons$competence$informant)]
coffee_consensus <- ethno_bayes_consensus(coffee_data, answers = 2,
                                          prior_for_answers = coffee_D)
round(coffee_consensus["1", ], 3)  # probability each use is in the consensus
#>        food        sale    medicine   technical    ornament       fence 
#>           0           1           0           0           0           0 
#>    firewood      timber       shade     hygiene animal_feed       share 
#>           0           0           0           0           0           0 
#>   pesticide      manure 
#>           0           0

Responses can also be counts: use answers = 11 for 0 to 10 reports per cell.

Modeling uses with covariates

homegardens_info and homegardens_species join to homegardens. Does a plant’s life form or native status predict medicinal use? Gardens are repeated measures, so a mixed model with a garden random effect is the right tool; lme4::glmer is shown if installed.

hg <- merge(merge(homegardens, homegardens_species, by = "sp_name"),
            homegardens_info, by = "informant")
hg <- droplevels(hg[hg$type != "Palm", ])  # one palm species: too few to estimate
fit <- glm(medicine ~ type + native + nearest_market_km, data = hg, family = binomial)
round(summary(fit)$coefficients, 2)
#>                     Estimate Std. Error z value Pr(>|z|)
#> (Intercept)            -2.62       0.23  -11.17     0.00
#> typeAnnual grass       -2.13       1.03   -2.08     0.04
#> typePerennial forb      0.75       0.21    3.68     0.00
#> typePerennial grass     0.40       0.33    1.19     0.23
#> typeShrub               0.39       0.20    1.95     0.05
#> typeTree                0.69       0.21    3.24     0.00
#> typeVine                1.19       0.32    3.77     0.00
#> native                  1.27       0.12   10.17     0.00
#> nearest_market_km      -0.05       0.04   -1.20     0.23
fit_mixed <- lme4::glmer(medicine ~ type + native + scale(nearest_market_km) + (1 | informant),
                         data = hg, family = binomial)
round(summary(fit_mixed)$coefficients, 2)
#>                          Estimate Std. Error z value Pr(>|z|)
#> (Intercept)                 -3.33       0.24  -14.05     0.00
#> typeAnnual grass            -2.34       1.05   -2.23     0.03
#> typePerennial forb           0.76       0.22    3.50     0.00
#> typePerennial grass          0.51       0.35    1.45     0.15
#> typeShrub                    0.55       0.22    2.52     0.01
#> typeTree                     0.81       0.23    3.54     0.00
#> typeVine                     1.32       0.35    3.83     0.00
#> native                       1.21       0.13    8.98     0.00
#> scale(nearest_market_km)    -0.08       0.12   -0.67     0.50

For a fully Bayesian version use brms with (1 | informant) + (1 | sp_name).

Summary

Use ethno_beta() for honest uncertainty in use probabilities, ethno_saturation() to judge sample size, and the consensus functions when you want a shared answer key and informant competence. For the theory behind the consensus models see Oravecz et al. (2014) and Romney et al. (1986). For the descriptive indices see the ‘Introduction to ethnobotanyR’ vignette. Data: Whitney et al. (2017) and Whitney et al. (2018).

References

Oravecz, Zita, Joachim Vandekerckhove, and William H. Batchelder. 2014. “Bayesian Cultural Consensus Theory.” Field Methods 26 (3): 207–22. https://doi.org/10.1177/1525822X13520280.
Romney, A. Kimball, Susan C. Weller, and William H. Batchelder. 1986. “Culture as Consensus: A Theory of Culture and Informant Accuracy.” American Anthropologist 88 (2): 313–38. https://www.jstor.org/stable/677564.
Whitney, Cory W., John R. S. Tabuti, Oliver Hensel, Ching-Hua Yeh, Jens Gebauer, and Eike Luedeling. 2017. “Homegardens and the Future of Food and Nutrition Security in Southwest Uganda.” Agricultural Systems 154: 133–44. https://doi.org/10.1016/j.agsy.2017.03.009.
Whitney, Cory, Joseph Bahati, and J. Gebauer. 2018. “Ethnobotany and Agrobiodiversity; Valuation of Plants in the Homegardens of Southwestern Uganda.” Ethnobiology Letters 9 (2): 90–100. https://doi.org/10.14237/ebl.9.2.2018.503.