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},
}
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.
| 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] 225homegardens_info has garden covariates and
homegardens_species has life form, family and native
status.
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() |
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.9991005Intervals 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 = "")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.4745763Compare 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.814boot_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.0155The 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.03203786ethno_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.
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 1Read 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)")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 0Responses can also be counts: use answers = 11 for 0 to
10 reports per cell.
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.23fit_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.50For a fully Bayesian version use brms with
(1 | informant) + (1 | sp_name).
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).