Learn R Programming

Behavioral Economic (be) Easy (ez) Demand

Behavioral economic demand is gaining in popularity. The motivation behind beezdemand was to create an alternative tool to conduct these analyses. It is meant for researchers to conduct behavioral economic (be) demand the easy (ez) way.

Note About Use

Currently, this version is stable. I encourage you to use it but be aware that, as with any software release, there might be (unknown) bugs present. I’ve tried hard to make this version usable while including the core functionality (described more below). However, if you find issues or would like to contribute, please open an issue on my GitHub page or email me.

Which Model Should I Use?

Your SituationRecommended ApproachLearn More
Single purchase task, individual fitsfit_demand_fixed()Fixed demand
Need group comparisons, random effectsfit_demand_mixed()Mixed demand
Many zeros, two-part modeling neededfit_demand_hurdle()Hurdle models
Cross-commodity substitutionfit_cp_*() functionsCross-price models

For detailed guidance on choosing the right modeling approach, see the model selection guide or vignette("model-selection"). Full documentation is available at the pkgdown site.

Installing beezdemand

CRAN Release (recommended method)

The latest stable version of beezdemand can be found on CRAN and installed using the following command. The first time you install the package, you may be asked to select a CRAN mirror. Simply select the mirror geographically closest to you.

install.packages("beezdemand")

library(beezdemand)

GitHub Release

To install a stable release directly from GitHub, first install and load the devtools package. Then, use install_github to install the package and associated vignette. You don’t need to download anything directly from GitHub, as you should use the following instructions:

install.packages("devtools")

devtools::install_github("brentkaplan/beezdemand", build_vignettes = TRUE)

library(beezdemand)

Using the Package

Example Dataset

An example dataset of responses on an Alcohol Purchase Task is provided. This object is called apt and is located within the beezdemand package. These data are a subset of from the paper by Kaplan & Reed (2018). Participants (id) reported the number of alcoholic drinks (y) they would be willing to purchase and consume at various prices (x; USD). Note the format of the data, which is called “long format”. Long format data are data structured such that repeated observations are stacked in multiple rows, rather than across columns. First, take a look at an extract of the dataset apt, where I’ve subsetted rows 1 through 10 and 17 through 26:

idxy
1190.010
2190.510
3191.010
4191.58
5192.08
6192.58
7193.07
8194.07
9195.07
10196.06
17300.03
18300.53
19301.03
20301.53
21302.02
22302.52
23303.02
24304.02
25305.02
26306.02

The first column contains the row number. The second column contains the id number of the series within the dataset. The third column contains the x values (in this specific dataset, price per drink) and the fourth column contains the associated responses (number of alcoholic drinks purchased at each respective price). There are replicates of id because for each series (or participant), several x values were presented.

Converting from Wide to Long and Vice Versa

For quick conversion, use the built-in convenience function:

long <- pivot_demand_data(wide, format = "long", id_var = "id")

Below is a manual walkthrough using tidyr for when you need more control.

Take for example the format of most datasets that would be exported from a data collection software such as Qualtrics or SurveyMonkey or Google Forms:

## the following code takes the apt data, which are in long format, and converts
## to a wide format that might be seen from data collection software
wide <- tidyr::pivot_wider(apt, names_from = x, values_from = y)
colnames(wide) <- c("id", paste0("price_", seq(1, 16, by = 1)))
knitr::kable(wide[1:5, 1:10])
idprice_1price_2price_3price_4price_5price_6price_7price_8price_9
19101010888777
30333322222
38444444433
6010108866554
6810109988765

A dataset such as this is referred to as “wide format” because each participant series contains a single row and multiple measurements within the participant are indicated by the columns. This data format is fine for some purposes; however, for beezdemand, data are required to be in “long format” (in the same format as the example data described earlier). In order to convert to the long format, some steps will be required.

First, it is helpful to rename the columns to what the prices actually were. For example, for the purposes of our example dataset, price_1 was $0.00 (free), price_2 was $0.50, price_3 was $1.00, and so on.

## make an object to hold what will be the new column names
newcolnames <- c("id", "0", "0.5", "1", "1.50", "2", "2.50", "3",
                 "4", "5", "6", "7", "8", "9", "10", "15", "20")
## current column names
colnames(wide)
 [1] "id"       "price_1"  "price_2"  "price_3"  "price_4"  "price_5" 
 [7] "price_6"  "price_7"  "price_8"  "price_9"  "price_10" "price_11"
[13] "price_12" "price_13" "price_14" "price_15" "price_16"
## replace current column names with new column names
colnames(wide) <- newcolnames

## how new data look (first 5 rows only)
knitr::kable(wide[1:5, ])
id00.511.5022.503456789101520
191010108887776655432
303333222222221111
384444444333322200
60101088665544332200
68101099887655544300

Now we can convert into a long format using some of the helpful functions in the tidyverse package (make sure the package is loaded before trying the commands below).

## using the dataframe 'wide', we specify the key will be 'price', the values
## will be 'consumption', and we will select all columns besides the first ('id')
long <- tidyr::pivot_longer(wide, -id, names_to = "price", values_to = "consumption")

## we'll sort the rows by id
long <- arrange(long, id)

## view the first 20 rows
knitr::kable(long[1:20, ])
idpriceconsumption
19010
190.510
19110
191.508
1928
192.508
1937
1947
1957
1966
1976
1985
1995
19104
19153
19202
3003
300.53
3013
301.503

Two final modifications we will make will be to (1) rename our columns to what the functions in beezdemand will expect to see: id, x, and y, and (2) ensure both x and y are in numeric format.

colnames(long) <- c("id", "x", "y")

long$x <- as.numeric(long$x)
long$y <- as.numeric(long$y)
knitr::kable(head(long))
idxy
190.010
190.510
191.010
191.58
192.08
192.58

The dataset is now “tidy” because: (1) each variable forms a column, (2) each observation forms a row, and (3) each type of observational unit forms a table (in this case, our observational unit is the Alcohol Purchase Task data). To learn more about the benefits of tidy data, readers are encouraged to consult Hadley Wikham’s essay on Tidy Data.

Obtain Descriptive Data

Descriptive statistics at each price (mean, SD, proportion of zeros, min, max) are available via get_descriptive_summary():

desc <- get_descriptive_summary(apt)
desc
Descriptive Summary of Demand Data
===================================

Call:
get_descriptive_summary(data = apt)

Data Summary:
  Subjects: 10
  Prices analyzed: 16

Statistics by Price:
 Price Mean Median   SD PropZeros NAs Min Max
     0  6.8    6.5 2.62       0.0   0   3  10
   0.5  6.8    6.5 2.62       0.0   0   3  10
     1  6.5    6.5 2.27       0.0   0   3  10
   1.5  6.1    6.0 1.91       0.0   0   3   9
     2  5.3    5.5 1.89       0.0   0   2   8
   2.5  5.2    5.0 1.87       0.0   0   2   8
     3  4.8    5.0 1.48       0.0   0   2   7
     4  4.3    4.5 1.57       0.0   0   2   7
     5  3.9    3.5 1.45       0.0   0   2   7
     6  3.5    3.0 1.43       0.0   0   2   6
     7  3.3    3.0 1.34       0.0   0   2   6
     8  2.6    2.5 1.51       0.1   0   0   5
     9  2.4    2.0 1.58       0.1   0   0   5
    10  2.2    2.0 1.32       0.1   0   0   4
    15  1.1    0.5 1.37       0.5   0   0   3
    20  0.8    0.0 1.14       0.6   0   0   3
PriceMeanMedianSDPropZerosNAsMinMax
06.86.52.620.00310
0.56.86.52.620.00310
16.56.52.270.00310
1.56.16.01.910.0039
25.35.51.890.0028
2.55.25.01.870.0028
34.85.01.480.0027
44.34.51.570.0027
53.93.51.450.0027
63.53.01.430.0026
73.33.01.340.0026
82.62.51.510.1005
92.42.01.580.1005
102.22.01.320.1004
151.10.51.370.5003
200.80.01.140.6003

A box-and-whisker plot is built in:

plot(desc)

Legacy equivalent: GetDescriptives(dat = apt, bwplot = TRUE). See vignette("migration-guide") for details.

Change Data

There are certain instances in which data are to be modified before fitting, for example when using an equation that logarithmically transforms y values. The following function can help with modifying data:

  • nrepl indicates number of replacement 0 values, either as an integer or "all". If this value is an integer, n, then the first n 0s will be replaced.

  • replnum indicates the number that should replace 0 values

  • rem0 removes all zeros

  • remq0e removes y value where x (or price) equals 0

  • replfree replaces where x (or price) equals 0 with a specified number

ChangeData(dat = apt, nrepl = 1, replnum = 0.01, rem0 = FALSE, remq0e = FALSE,
           replfree = NULL)

Identify Unsystematic Responses

Stein et al.’s (2015) algorithm for identifying unsystematic responses is available via check_systematic_demand():

sys_check <- check_systematic_demand(apt)
sys_check
Systematicity Check (demand)
------------------------------ 
Total patterns: 10 
Systematic: 10 ( 100 %)
Unsystematic: 0 ( 0 %)

Use summary() for details, tidy() for per-subject results.
summary(sys_check)
Systematicity Check Summary (demand)
================================================== 

Total patterns: 10 
Systematic: 10 ( 100 %)
Unsystematic: 0 ( 0 %)

Failures by Criterion:
# A tibble: 4 × 3
  criterion n_fail pct_fail
  <chr>      <int>    <dbl>
1 trend          0        0
2 bounce         0        0
3 reversals      0        0
4 overall        0        0

Legacy equivalent: CheckUnsystematic(dat = apt, deltaq = 0.025, bounce = 0.1, reversals = 0, ncons0 = 2). See vignette("migration-guide") for details.

Analyze Demand Data

Obtaining Empirical Measures

Empirical measures (intensity, breakpoint, Omax, Pmax) can be obtained via get_empirical_measures():

emp <- get_empirical_measures(apt)
emp
Empirical Demand Measures
=========================

Call:
get_empirical_measures(data = apt)

Data Summary:
  Subjects: 10
  Subjects with zero consumption: Yes
  Complete cases (no NAs): 6

Empirical Measures:
  id Intensity BP0 BP1 Omaxe Pmaxe
  19        10  NA  20    45    15
  30         3  NA  20    20    20
  38         4  15  10    21     7
  60        10  15  10    24     8
  68        10  15  10    36     9
 106         5   8   7    15     5
 113         6  NA  20    45    15
 142         8  NA  20    60    20
 156         7  20  15    21     7
 188         5  15  10    15     5
idIntensityBP0BP1OmaxePmaxe
1910NA204515
303NA202020
3841510217
60101510248
68101510369

Legacy equivalent: GetEmpirical(dat = apt). See vignette("migration-guide") for details.

Fitting Demand Curves

The recommended function for fitting individual demand curves is fit_demand_fixed(). It provides a modern S3 interface with summary(), coef(), tidy(), glance(), predict(), and plot() methods.

Key arguments:

  • equation"hs" (Hursh & Silberberg, 2008; default) or "koff" (Koffarnus et al., 2015).
  • k — scaling constant. By default, calculated from the sample range + 0.5. Other options: "ind" (individual), "fit" (free parameter), "share" (shared across all series).
  • aggNULL (individual fits; default), "Mean" (fit to averaged data), or "Pooled" (fit to all data ignoring clustering).

Individual fits (Hursh & Silberberg equation)

fit_hs <- fit_demand_fixed(apt, equation = "hs")
fit_hs
Fixed-Effect Demand Model
==========================

Call:
fit_demand_fixed(data = apt, equation = "hs")

Equation: hs 
k: fixed (2) 
Subjects: 10 ( 10 converged, 0 failed)

Use summary() for parameter summaries, tidy() for tidy output.

Extract coefficients and tidy output:

head(coef(fit_hs))
# A tibble: 6 × 5
  id    term  estimate estimate_scale term_display
  <chr> <chr>    <dbl> <chr>          <chr>       
1 19    q0    10.2     natural        q0          
2 19    alpha  0.00205 natural        alpha       
3 30    q0     2.81    natural        q0          
4 30    alpha  0.00587 natural        alpha       
5 38    q0     4.50    natural        q0          
6 38    alpha  0.00420 natural        alpha       
idtermestimatestd.errorstatisticp.valuecomponentestimate_scaleterm_displayestimate_internal
19Q010.1586650.2685323NANAfixednaturalQ010.158665
30Q02.8073660.2257764NANAfixednaturalQ02.807366
38Q04.4974560.2146862NANAfixednaturalQ04.497456
60Q09.9242740.4591683NANAfixednaturalQ09.924274
68Q010.3903840.3290277NANAfixednaturalQ010.390384
106Q05.6835660.3002817NANAfixednaturalQ05.683566
113Q06.1959490.1744096NANAfixednaturalQ06.195949
142Q06.1719900.6408575NANAfixednaturalQ06.171990
156Q08.3489730.4105617NANAfixednaturalQ08.348973
188Q06.3036390.5636959NANAfixednaturalQ06.303639

Koffarnus equation

fit_koff <- fit_demand_fixed(apt, equation = "koff")
fit_koff
Fixed-Effect Demand Model
==========================

Call:
fit_demand_fixed(data = apt, equation = "koff")

Equation: koff 
k: fixed (2) 
Subjects: 10 ( 10 converged, 0 failed)

Use summary() for parameter summaries, tidy() for tidy output.

Mean curve

fit_mean <- fit_demand_fixed(apt, equation = "hs", agg = "Mean")
fit_mean
Fixed-Effect Demand Model
==========================

Call:
fit_demand_fixed(data = apt, equation = "hs", agg = "Mean")

Equation: hs 
k: fixed (2) 
Aggregation: Mean 
Subjects: 1 ( 1 converged, 0 failed)

Use summary() for parameter summaries, tidy() for tidy output.

Shared k

fit_share <- fit_demand_fixed(apt, equation = "hs", k = "share")
Beginning search for best-starting k

Best k found at 0.93813356574003 = err: 0.744881846162718

Searching for shared K, this can take a while...
fit_share
Fixed-Effect Demand Model
==========================

Call:
fit_demand_fixed(data = apt, equation = "hs", k = "share")

Equation: hs 
k: share 
Subjects: 10 ( 10 converged, 0 failed)

Use summary() for parameter summaries, tidy() for tidy output.

Plotting Demand Curves

All fit_demand_fixed() results support plot():

plot(fit_hs, type = "individual", x_trans = "log10")
Free is shown as `0.01` for purposes of plotting.
plot(fit_mean, x_trans = "log10")
Free is shown as `0.01` for purposes of plotting.

Legacy equivalent: The FitCurves() + PlotCurves() workflow is still available for backward compatibility. See vignette("migration-guide") for transitioning from FitCurves() to fit_demand_fixed().

Compare Values of $\alpha$ and $Q_0$ via Extra Sum-of-Squares F-Test

For mixed-effects group comparisons, consider fit_demand_mixed() with group factors. See vignette("group-comparisons").

When one has multiple groups, it may be beneficial to compare whether separate curves are preferred over a single curve. This is accomplished by the Extra Sum-of-Squares F-test. This function (using the argument compare) will determine whether a single $\alpha$ or a single $Q_0$ is better than multiple $\alpha$s or $Q_0$s. A single curve will be fit, the residual deviations calculated and those residuals are compared to residuals obtained from multiple curves. A resulting F statistic will be reporting along with a p value.

## setting the seed initializes the random number generator so results will be
## reproducible
set.seed(1234)

## manufacture random grouping
apt$group <- NA
apt[apt$id %in% sample(unique(apt$id), length(unique(apt$id))/2), "group"] <- "a"
apt$group[is.na(apt$group)] <- "b"

## take a look at what the new groupings look like in long form
knitr::kable(apt[1:20, ])
idxygroup
190.010a
190.510a
191.010a
191.58a
192.08a
192.58a
193.07a
194.07a
195.07a
196.06a
197.06a
198.05a
199.05a
1910.04a
1915.03a
1920.02a
300.03b
300.53b
301.03b
301.53b
## in order for this to run, you will have had to run the code immediately
## preceeding (i.e., the code to generate the groups)
ef <- ExtraF(dat = apt, equation = "koff", k = 2, groupcol = "group", verbose = TRUE)
Null hypothesis: alpha same for all data sets

Alternative hypothesis: alpha different for each data set

Conclusion: fail to reject the null hypothesis

F(1,156) = 0.0298, p = 0.8631

A summary table (broken up here for ease of display) will be created when the option verbose = TRUE. This table can be accessed as the dfres object resulting from ExtraF. In the example above, we can access this summary table using ef$dfres:

GroupQ0dKR2Alpha
SharedNANANANA
a8.48963420.62064440.0040198
b5.84811920.62064440.0040198
Not SharedNANANANA
a8.50344220.64488010.0040518
b5.82207520.52428250.0039376

Fitted Measures

GroupNAbsSSSdRes
SharedNANANA
a160387.09451.570213
b160387.09451.570213
Not SharedNANANA
a80249.27641.787695
b80137.74401.328890

Uncertainty and Model Information

GroupEVOmaxdPmaxd
SharedNANANA
a0.879530122.631598.453799
b0.879530122.6315912.272265
Not SharedNANANA
a0.872574122.452608.373320
b0.897894523.1041412.584550

Derived Measures

GroupOmaxaNotes
SharedNANA
a22.63190converged
b22.63190converged
Not SharedNANA
a22.45291converged
b23.10445converged

Convergence and Summary Information

When verbose = TRUE, objects from the result can be used in subsequent graphing. The following code generates a plot of our two groups. We can use the predicted values already generated from the ExtraF function by accessing the newdat object. In the example above, we can access these predicted values using ef$newdat. Note that we keep the linear scaling of y given we used Koffarnus et al. (2015)’s equation fitted to the data.

## be sure that you've loaded the tidyverse package (e.g., library(tidyverse))
ggplot(apt, aes(x = x, y = y, group = group)) +
  ## the predicted lines from the sum of squares f-test can be used in subsequent
  ## plots by calling data = ef$newdat
  geom_line(aes(x = x, y = y, group = group, color = group),
            data = ef$newdat[ef$newdat$x >= .1, ]) +
  stat_summary(fun.data = "mean_se", aes(color = group),
               geom = "errorbar", orientation = "x", width = 0) +
  stat_summary(fun = "mean", aes(fill = group), geom = "point", shape = 21,
               color = "black", stroke = .75, size = 4, orientation = "x") +
  scale_x_continuous(limits = c(.4, 50), breaks = c(.1, 1, 10, 100)) +
  coord_trans(x = "log10") +
  scale_color_discrete(name = "Group") +
  scale_fill_discrete(name = "Group") +
  labs(x = "Price per Drink", y = "Drinks Purchased") +
  theme(legend.position = c(.85, .75)) +
  ## theme_apa is a beezdemand function used to change the theme in accordance
  ## with American Psychological Association style
  theme_apa()

Cross-Price Demand Models

In addition to classic purchase-task analyses, beezdemand now includes functions for cross-price demand modeling. These tools help you check for unsystematic data, fit nonlinear or linear/mixed-effects cross-price models, and visualize the results.

Key functions:

  • check_unsystematic_cp() — identify unsystematic cross-price patterns.
  • fit_cp_nls() — fit nonlinear cross-price models (e.g., exponentiated form).
  • fit_cp_linear() — fit linear and mixed-effects cross-price models.
  • S3 methods: summary(), plot(), glance(), tidy().

Minimal example (using the included ETM dataset):

library(dplyr)
data(etm, package = "beezdemand")

# Focus on one product/id and check for unsystematic responding
ex <- etm |> filter(group %in% "E-Cigarettes", id %in% 1)
check_unsystematic_cp(ex)

# Nonlinear cross-price model (exponentiated form)
fit_nls <- fit_cp_nls(ex, equation = "exponentiated", return_all = TRUE)
summary(fit_nls)
plot(fit_nls, x_trans = "log10")

Linear mixed-effects cross-price model across all participants:

fit_mixed <- fit_cp_linear(
  etm,
  type = "mixed",
  log10x = TRUE,
  group_effects = "interaction",
  return_all = TRUE
)
summary(fit_mixed)
plot(fit_mixed, x_trans = "log10", pred_type = "all")

See the vignette “How to Use Cross-Price Demand Model Functions” for a full walkthrough of data structure, modeling options, visualization, and post-hoc comparisons.

Mixed-Effects Demand Models

beezdemand also supports nonlinear mixed-effects demand models to estimate subject-level parameters (e.g., Q0 and alpha) while modeling fixed effects of conditions (e.g., dose, drug). The zben equation form pairs well with the included LL4 transformation to handle zeros and wide dynamic ranges.

Key functions:

  • fit_demand_mixed() — fit mixed-effects demand models via nlme.
  • ll4() / ll4_inv() — transform and inverse-transform consumption.
  • Plotting and predictions via plot()/predict() on beezdemand_nlme objects.
  • Post-hoc summaries with get_demand_param_emms() and comparisons using get_demand_comparisons().

Minimal example (using the included nonhuman dataset ko):

library(dplyr)
data(ko, package = "beezdemand")

# Fit zben form on LL4-transformed consumption with two factors
fit_nlme <- fit_demand_mixed(
  data = ko,
  y_var = "y_ll4",
  x_var = "x",
  id_var = "monkey",
  factors = c("drug", "dose"),
  equation_form = "zben"
)
print(fit_nlme)

# Plot on the natural (back-transformed) scale
plot(
  fit_nlme,
  inv_fun = ll4_inv,
  x_trans = "pseudo_log",
  y_trans = "pseudo_log"
)

For more details, see the “Mixed-Effects Demand Modeling with beezdemand” vignette, which covers starting values, fixed/random effects, and post-hoc analyses of parameter estimates.

Learn More About Functions

To learn more about a function and what arguments it takes, type “?” in front of the function name.

## Modern interface (recommended)
?fit_demand_fixed
?get_empirical_measures
?get_descriptive_summary
?check_systematic_demand

## Legacy interface (still available)
?FitCurves
?CheckUnsystematic

Acknowledgments

  • Shawn P. Gilroy, Contributor GitHub

  • Derek D. Reed, Applied Behavioral Economics Laboratory

  • Mikhail N. Koffarnus, Addiction Recovery Research Center

  • Steven R. Hursh, Institutes for Behavior Resources, Inc.

  • Paul E. Johnson, Center for Research Methods and Data Analysis, University of Kansas

  • Peter G. Roma, Institutes for Behavior Resources, Inc.

  • W. Brady DeHart, Addiction Recovery Research Center

  • Michael Amlung, Cognitive Neuroscience of Addictions Laboratory

Special thanks to the following people who helped provide feedback on this document:

  • Alexandra M. Mellis

  • Mr. Jeremiah “Downtown Jimbo Brown” Brown

  • Gideon Naudé

LLM Docs

The package publishes machine-readable documentation for use with AI coding assistants and RAG systems:

  • llms.txt — canonical entry point for LLMs, published at: https://brentkaplan.github.io/beezdemand/llms.txt
  • Context7 — a context7.json at the repo root configures Context7 indexing. Use /brentkaplan/beezdemand as the library ID in Context7-enabled tools.
  • Docs map — a chunkable reference at inst/llm/docs-map.md summarises workflows, data format, and key functions for RAG ingestion.

Recommended Readings

  • Reed, D. D., Niileksela, C. R., & Kaplan, B. A. (2013). Behavioral economics: A tutorial for behavior analysts in practice. Behavior Analysis in Practice, 6 (1), 34–54. https://doi.org/10.1007/BF03391790

  • Reed, D. D., Kaplan, B. A., & Becirevic, A. (2015). Basic research on the behavioral economics of reinforcer value. In Autism Service Delivery (pp. 279-306). Springer New York. https://doi.org/10.1007/978-1-4939-2656-5_10

  • Hursh, S. R., & Silberberg, A. (2008). Economic demand and essential value. Psychological Review, 115 (1), 186-198. https://doi.org/10.1037/0033-295X.115.1.186

  • Koffarnus, M. N., Franck, C. T., Stein, J. S., & Bickel, W. K. (2015). A modified exponential behavioral economic demand model to better describe consumption data. Experimental and Clinical Psychopharmacology, 23 (6), 504-512. https://doi.org/10.1037/pha0000045

  • Stein, J. S., Koffarnus, M. N., Snider, S. E., Quisenberry, A. J., & Bickel, W. K. (2015). Identification and management of nonsystematic purchase task data: Toward best practice. Experimental and Clinical Psychopharmacology 23 (5), 377-386. https://doi.org/10.1037/pha0000020

  • Hursh, S. R., Raslear, T. G., Shurtleff, D., Bauman, R., & Simmons, L. (1988). A cost‐benefit analysis of demand for food. Journal of the Experimental Analysis of Behavior, 50 (3), 419-440. https://doi.org/10.1901/jeab.1988.50-419

  • Kaplan, B. A., Franck, C. T., McKee, K., Gilroy, S. P., & Koffarnus, M. N. (2021). Applying mixed-effects modeling to behavioral economic demand: An introduction. Perspectives on Behavior Science, 44 (2), 333–358. https://doi.org/10.1007/s40614-021-00299-7

  • Koffarnus, M. N., Kaplan, B. A., Franck, C. T., Rzeszutek, M. J., & Traxler, H. K. (2022). Behavioral economic demand modeling chronology, complexities, and considerations: Much ado about zeros. Behavioural Processes, 199, 104646. https://doi.org/10.1016/j.beproc.2022.104646

  • Reed, D. D., Kaplan, B. A., & Gilroy, S. P. (2025). Handbook of Operant Behavioral Economics: Demand, Discounting, Methods, and Applications (1st ed.). Academic Press. https://shop.elsevier.com/books/handbook-of-operant-behavioral-economics/reed/978-0-323-95745-8

  • Kaplan, B. A. (2025). Quantitative models of operant demand. In D. D. Reed, B. A. Kaplan, & S. P. Gilroy (Eds.), Handbook of Operant Behavioral Economics: Demand, Discounting, Methods, and Applications (1st ed.). Academic Press. https://shop.elsevier.com/books/handbook-of-operant-behavioral-economics/reed/978-0-323-95745-8

  • Kaplan, B. A., & Reed, D. D. (2025). shinybeez: A Shiny app for behavioral economic easy demand and discounting. Journal of the Experimental Analysis of Behavior. https://doi.org/10.1002/jeab.70000

  • Rzeszutek, M. J., Regnier, S. D., Franck, C. T., & Koffarnus, M. N. (2025). Overviewing the exponential model of demand and introducing a simplification that solves issues of span, scale, and zeros. Experimental and Clinical Psychopharmacology.

  • Rzeszutek, M. J., Regnier, S. D., Kaplan, B. A., Traxler, H. K., Stein, J. S., Tomlinson, D., & Koffarnus, M. N. (2025). Identification and management of nonsystematic cross-commodity data: Toward best practice. Experimental and Clinical Psychopharmacology. In press.

Copy Link

Version

Install

install.packages('beezdemand')

Monthly Downloads

298

Version

0.2.0

License

GPL (>= 2)

Issues

Pull Requests

Stars

Forks

Maintainer

Brent Kaplan

Last Published

March 3rd, 2026

Functions in beezdemand (0.2.0)

calc_omax_pmax

Calculate Omax and Pmax for Demand Curves
ReplaceZeros

Replace Zeros
augment.beezdemand_fixed

Augment a beezdemand_fixed Model with Fitted Values and Residuals
beezdemand_empirical_methods

S3 Methods for beezdemand_empirical Objects
build_fixed_rhs

Build Fixed-Effects RHS Formula String
calculate_amplitude_persistence

Calculate Amplitude and Persistence
SimulateDemand

Simulate Demand Data
annotation_logticks2

annotation_logticks2
calc_omax_pmax_vec

Calculate Omax and Pmax for Multiple Subjects
beezdemand_calc_pmax_omax

Calculate Pmax and Omax with Method Reporting and Parameter-Space Safety
apt

Example alcohol purchase task data
beezdemand-package

beezdemand: Behavioral Economic Easy Demand
calc_observed_pmax_omax

Calculate Observed Pmax/Omax Grouped by ID
PlotCurves

Plot Curves
RecodeOutliers

Recode Outliers
beezdemand_calc_pmax_omax_vec

Calculate Pmax/Omax for Multiple Subjects
calc_group_metrics

Calculate Group-Level Demand Metrics
beezdemand_descriptive_methods

S3 Methods for beezdemand_descriptive Objects
coef.beezdemand_nlme

Extract Coefficients from a beezdemand_nlme Model
coef.beezdemand_hurdle

Extract Coefficients from Hurdle Demand Model
check_demand_model

Check Demand Model Diagnostics
coef-methods

Extract Coefficients from Cross-Price Demand Models
coef.beezdemand_fixed

Extract Coefficients from Fixed-Effect Demand Fit
compare_hurdle_models

Compare Nested Hurdle Demand Models
check_unsystematic_cp

Check for Unsystematic Patterns in Cross-Price Data
check_systematic_demand

Check Demand Data for Unsystematic Responding
collapse_factor_levels

Collapse Factor Levels for a Specific Parameter
compare_models

Compare Demand Models
confint.beezdemand_fixed

Confidence Intervals for Fixed-Effect Demand Model Parameters
cannabisCigarettes

Cannabis/cigarette cross-price responses
confint.beezdemand_nlme

Confidence Intervals for Mixed-Effects Demand Model Parameters
confint.beezdemand_hurdle

Confidence Intervals for Hurdle Demand Model Parameters
cp_posthoc_slopes

Run pairwise slope comparisons for cross-price demand model
confint.cp_model_nls

Confidence Intervals for Cross-Price NLS Model Parameters
cp_posthoc_intercepts

Run pairwise intercept comparisons for cross-price demand model
check_systematic_cp

Check Cross-Price Data for Unsystematic Responding
cp

Example cross‐price dataset
.beezdemand_param_registry

Canonical Parameter Definitions
.elasticity_at_price

Calculate Elasticity at a Given Price
.hurdle_build_tmb_data

Build TMB Data List for Hurdle Model
.calc_alpha_star

Compute Normalized Alpha (Alpha Star) via the Delta Method
.hurdle_extract_estimates

Extract Results from TMB Hurdle Fit
.hurdle_default_starts

Generate Default Starting Values for Hurdle Model
.hurdle_compute_subject_pars

Compute Subject-Specific Parameters
.beezdemand_derived_registry

Derived Metrics Registry
.beezdemand_equation_registry

Equation Registry
.pmax_analytic_hurdle

Analytic Pmax for Hurdle Model (Natural-parameter exponential form)
.hurdle_transform_random_effects

Transform Random Effects to Original Scale
.pmax_analytic_hurdle_hs_stdq0

Analytic Pmax for HS-Standardized Hurdle Part II (Q0 inside exponent)
.hurdle_get_param_names

Get Parameter Names for Hurdle Model
fit_cp_linear

Fit a Linear Cross-Price Demand Model
.hurdle_normalize_starts

Normalize User-Provided Starting Values
.check_unit_elasticity

Check Unit Elasticity Condition
.calc_observed_pmax_omax

Calculate Observed Pmax and Omax for a Single Subject
.hurdle_prepare_data

Prepare Hurdle Model Data
.pmax_analytic_hs

Analytic Pmax for HS/Exponential Model (Lambert W)
fit_demand_fixed

Fit Fixed-Effect Demand Curves
fit_demand_hurdle

Fit Two-Part Mixed Effects Hurdle Demand Model
etm

Example Experimental Tobacco Marketplace data
fit_cp_nls

Fit cross-price demand with NLS (+ robust fallbacks)
.pmax_analytic_snd

Analytic Pmax for Simplified/SND Model
.pmax_numerical

Numerical Pmax via Optimization
.to_natural_scale

Convert Parameter from Specified Scale to Natural Scale
.standardize_params_to_natural

Validate and Convert Parameters to Natural Scale
get_demand_param_emms

Get Estimated Marginal Means for Demand Parameters
extract_coefficients

Extract All Coefficient Types from Cross-Price Demand Models
get_demand_comparisons

Get Pairwise Comparisons for Demand Parameters
get_demand_param_trends

Get Trends (Slopes) of Demand Parameters with respect to Continuous Covariates
get_descriptive_summary

Calculate Descriptive Statistics by Price
fixef.beezdemand_nlme

Extract Fixed Effects from a beezdemand_nlme Model
get_observed_demand_param_emms

Get Estimated Marginal Means for Observed Factor Combinations
get_subject_pars

Get Subject-Specific Parameters
glance.cp_model_lm

Get model summaries from a linear cross-price model
glance.beezdemand_hurdle

Glance at a beezdemand_hurdle Model
glance.cp_model_lmer

Get model summaries from a mixed-effects cross-price model
get_empirical_measures

Calculate Empirical Demand Measures
glance.beezdemand_fixed

Glance Method for beezdemand_fixed
fit_demand_mixed

Fit Nonlinear Mixed-Effects Demand Model
fixed-demand

Fixed-Effect Demand Curve Fitting
ll4

Log-Logistic Transformation (LL4-like)
glance.beezdemand_systematicity

Glance Method for beezdemand_systematicity
glance.beezdemand_nlme

Glance method for beezdemand_nlme
get_canonical_param

Get Canonical Parameter Name
get_pooled_nls_starts

Get Starting Values from a Pooled NLS Model (Internal Helper)
new_beezdemand_systematicity

Create a beezdemand_systematicity Object
ko

Example nonhuman demand data with drug and dose
get_legacy_mapping

Legacy to Canonical Mapping Table
lambertW

Lambert W
fixef.cp_model_lmer

Extract Fixed Effects from Mixed-Effects Cross-Price Model
get_hurdle_param_summary

Get Hurdle Model Parameter Summary
logLik.beezdemand_hurdle

Extract Log-Likelihood from Hurdle Demand Model
format_param_name

Format Parameter Name with Scale Prefix
ongoingETM

Experimental Tobacco Marketplace (ETM) data
get_equation_spec

Get Equation Specification
plot-theme

beezdemand Plot Theme and Color Palette
pivot_demand_data

Reshape Demand Data Between Wide and Long Formats
plot.beezdemand_fixed

Plot Method for beezdemand_fixed
get_canonical_metric

Get Canonical Derived Metric Name
ll4_inv

Inverse Log-Logistic Transformation (Inverse LL4-like)
lowNicClean

Low-nicotine cigarette purchase task
print.beezdemand_fixed

Print Method for beezdemand_fixed
print.beezdemand_diagnostics

Print Method for Model Diagnostics
plot_residuals

Plot Residual Diagnostics
plot_qq

Plot Random Effects Q-Q
plot.cp_model_lmer

Plot Method for Mixed-Effects Cross-Price Demand Models
print.summary.cp_model_lm

Print method for summary.cp_model_lm objects.
print.summary.cp_model_lmer

Print method for summary.cp_model_lmer objects.
plot.beezdemand_hurdle

Plot Demand Curves from Hurdle Demand Model
palette_beezdemand

beezdemand Color Palette
param-registry

Parameter Naming Registry for beezdemand
get_individual_coefficients

Calculate Individual-Level Predicted Coefficients from beezdemand_nlme Model
get_k

Calculate K Scaling Parameter for Demand Curve Fitting
print.anova.beezdemand_hurdle

Print Method for ANOVA Comparisons
print.beezdemand_comparison

Print method for beezdemand_comparison objects
print.summary.beezdemand_fixed

Print Method for summary.beezdemand_fixed
print.summary.beezdemand_nlme

Print method for summary.beezdemand_nlme
print.summary.beezdemand_systematicity

Print Method for summary.beezdemand_systematicity
plot.cp_model_nls

Plot a Cross-Price Demand Model (Nonlinear)
print_mc_summary

Print Monte Carlo Simulation Results
pseudo_ll4_trans

Create a Pseudo-Log LL4 Transformation Object for ggplot2
predict.cp_model_nls

Predict from a Cross-Price Demand Model (Nonlinear)
predict.cp_model_lmer

Predict from a Mixed-Effects Cross-Price Demand Model
reexports

Objects exported from other packages
ranef.cp_model_lmer

Extract Random Effects from Mixed-Effects Cross-Price Model
print.summary.beezdemand_hurdle

Print Summary of Hurdle Demand Model
glance.cp_model_nls

Get model summaries from a cross-price model
pull

Pull
tidy.beezdemand_fixed

Tidy Method for beezdemand_fixed
ranef.beezdemand_nlme

Extract Random Effects from a beezdemand_nlme Model
summary.beezdemand_nlme

Summary method for beezdemand_nlme
summary.beezdemand_hurdle

Summarize a Hurdle Demand Model Fit
has_significant_interaction

Test for significant interaction in a cross-price demand model
theme_beezdemand

beezdemand Plot Theme
theme_apa

APA Theme
predict.beezdemand_fixed

Predict Method for beezdemand_fixed
plot.beezdemand_nlme

Plot Method for beezdemand_nlme Objects
print.beezdemand_nlme

Print Method for beezdemand_nlme Objects
plot.cp_model_lm

Plot Method for Linear Cross-Price Demand Models
pmax-omax-engine

Pmax/Omax Engine
summary.cp_model_nls

Summarize a Cross-Price Demand Model (Nonlinear)
summary.cp_model_lmer

Summary method for cp_model_lmer objects.
plot_subject

Plot Demand Curve for a Single Subject
predict.beezdemand_nlme

Predict Method for beezdemand_nlme Objects
predict.cp_model_lm

Predict method for cp_model_lm objects.
print.beezdemand_summary

Print Method for beezdemand Summary Objects
tidy.beezdemand_hurdle

Tidy a beezdemand_hurdle Model
tidy.cp_model_nls

Convert a cross-price model to a tidy data frame of coefficients
predict.beezdemand_hurdle

Predict Method for Hurdle Demand Models
print.cp_posthoc

Print method for cp_posthoc objects
validate_collapse_levels

Validate Collapse Levels Structure
validate_cp_data

Validate and Filter Cross-Price Demand Data
validate_demand_data

Validate and Prepare Demand Data
scale_fill_beezdemand

beezdemand Fill Scale (Discrete)
print.beezdemand_systematicity

Print Method for beezdemand_systematicity
scale_ll4

Create an LL4-like Scale for ggplot2 Axes
print.summary.cp_unsystematic

Print Method for Cross-Price Unsystematic Summary
print.summary.cp_model_nls

Print method for summary.cp_model_nls objects
summary.cp_model_lm

Summary method for cp_model_lm objects.
summary.beezdemand_systematicity

Summary Method for beezdemand_systematicity
simulate_hurdle_data

Simulate Data from Two-Part Mixed Effects Hurdle Demand Model
summary.beezdemand_fixed

Summary Method for beezdemand_fixed
print.beezdemand_model_comparison

Print Method for Model Comparison
print.beezdemand_hurdle

Print Method for Hurdle Demand Model
run_hurdle_monte_carlo

Run Monte Carlo Simulation Study for Hurdle Demand Model
scale_color_beezdemand

beezdemand Color Scale (Discrete)
systematic-wrappers

Systematicity Check Wrappers
summary.cp_unsystematic

Summarize Cross-Price Unsystematic Data Check Results
tidy.beezdemand_nlme

Tidy method for beezdemand_nlme
tidy.cp_model_lm

Extract coefficients from a linear cross-price model in tidy format
tidy.beezdemand_systematicity

Tidy Method for beezdemand_systematicity
validate_param_scale

Validate Parameter Scale
tidy.cp_model_lmer

Extract coefficients from a mixed-effects cross-price model in tidy format
validate_hurdle_data

Validate Hurdle Demand Data
ChangeData

ChangeData
CheckCols

Check Column Names
BIC.beezdemand_hurdle

BIC for Hurdle Demand Model
AIC.beezdemand_hurdle

AIC for Hurdle Demand Model
FitMeanCurves

Fit Pooled/Mean Curves
CheckUnsystematic

Systematic Purchase Task Data Checker
FitCurves

FitCurves
ExtraF

ExtraF
GetSharedK

Get Shared K
GetValsForSim

Get Values for SimulateDemand
GetAnalyticPmax

Get pmax
GetAnalyticPmaxFallback

Analytic Pmax Fallback
GetDescriptives

Get Purchase Task Descriptive Summary
GetK

Get K
anova.beezdemand_hurdle

ANOVA Method for Hurdle Demand Models
augment.beezdemand_hurdle

Augment a beezdemand_hurdle Model with Fitted Values and Residuals
anova.beezdemand_nlme

ANOVA Method for NLME Demand Models
augment.beezdemand_nlme

Augment a beezdemand_nlme Model with Fitted Values and Residuals
PlotCurve

Plot Curve
GetEmpirical

GetEmpirical
apt_full

Full alcohol purchase task dataset