Fixed-Effect Demand Modeling with `beezdemand`

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  dev = "png",
  dpi = 144,
  fig.width = 7,
  fig.height = 5,
  warning = FALSE,
  message = FALSE,
  cache = TRUE,
  cache.path = "fixed-demand_cache/",
  autodep = TRUE
)

library(beezdemand)
library(dplyr)
library(ggplot2)

data("apt", package = "beezdemand", envir = environment())

cache_key_object <- function(x) {
  tmp <- tempfile(fileext = ".rds")
  saveRDS(x, tmp)
  on.exit(unlink(tmp), add = TRUE)
  unname(tools::md5sum(tmp))
}

knitr::opts_chunk$set(
  cache.extra = list(
    beezdemand_version = as.character(utils::packageVersion("beezdemand")),
    apt = cache_key_object(apt)
  )
)

Introduction

This vignette demonstrates how to fit individual (fixed-effect) demand curves using fit_demand_fixed(). This function fits separate nonlinear least squares (NLS) models for each subject, producing per-subject estimates of demand parameters like $Q_{0}$ (intensity) and $\alpha$ (elasticity).

When to use fixed-effect models: Fixed-effect models are appropriate when you want independent curve fits for each participant. They make no assumptions about the distribution of parameters across subjects and are the simplest approach to demand curve analysis. For hierarchical models that share information across subjects, see vignette("mixed-demand"). For guidance on choosing between approaches, see vignette("model-selection").

Data format: All modeling functions expect long-format data with columns id (subject identifier), x (price), and y (consumption). See vignette("beezdemand") for details on data preparation and conversion from wide format.

Fitting with Different Equations

fit_demand_fixed() supports several equation forms. We demonstrate the three most common below using the apt (Alcohol Purchase Task) dataset.

Hursh & Silberberg ("hs")

The exponential model of demand (Hursh & Silberberg, 2008):

$$\log_{10}(Q) = \log_{10}(Q_0) + k \cdot (e^{-\alpha \cdot Q_0 \cdot x} - 1)$$

fit_hs <- fit_demand_fixed(apt, equation = "hs", k = 2)
fit_hs

Koffarnus ("koff")

The exponentiated model (Koffarnus et al., 2015):

$$Q = Q_0 \cdot 10^{k \cdot (e^{-\alpha \cdot Q_0 \cdot x} - 1)}$$

fit_koff <- fit_demand_fixed(apt, equation = "koff", k = 2)
fit_koff

Simplified ("simplified")

The simplified exponential model (Rzeszutek et al., 2025) does not require a scaling constant $k$:

$$Q = Q_0 \cdot e^{-\alpha \cdot Q_0 \cdot x}$$

fit_simplified <- fit_demand_fixed(apt, equation = "simplified")
fit_simplified

The k Parameter

The scaling constant $k$ controls the range of the demand function. For the "hs" and "koff" equations, k can be specified in several ways:

| k value | Behavior | |-----------|----------| | Numeric (e.g., 2) | Fixed constant for all subjects (default) | | "ind" | Individual $k$ per subject, computed from each subject's data range | | "share" | Single shared $k$ estimated across all subjects via global regression | | "fit" | $k$ is a free parameter estimated jointly with $Q_0$ and $\alpha$ |

## Fixed k (default)
fit_demand_fixed(apt, equation = "hs", k = 2)

## Individual k per subject
fit_demand_fixed(apt, equation = "hs", k = "ind")

## Shared k across subjects
fit_demand_fixed(apt, equation = "hs", k = "share")

## Fitted k as free parameter
fit_demand_fixed(apt, equation = "hs", k = "fit")

The param_space argument controls whether optimization is performed on the natural scale ("natural", the default) or log10 scale ("log10"). The log10 scale can improve convergence for some datasets:

fit_demand_fixed(apt, equation = "hs", k = 2, param_space = "log10")

Inspecting Fits

All beezdemand_fixed objects support the standard tidy(), glance(), augment(), and confint() methods for programmatic access to results.

tidy(): Per-Subject Parameter Estimates

tidy(fit_hs)

glance(): Model-Level Summary

glance(fit_hs)

augment(): Fitted Values and Residuals

augment(fit_hs)

confint(): Confidence Intervals

confint(fit_hs)

summary(): Formatted Summary

The summary() method provides a formatted overview including parameter distributions across subjects:

summary(fit_hs)

Normalized Alpha ($\alpha^*$)

When $k$ varies across participants or studies (e.g., k = "ind" or k = "fit"), raw $\alpha$ values are not directly comparable. The alpha_star column in tidy() output provides a normalized version (Strategy B; Rzeszutek et al., 2025) that adjusts for the scaling constant:

$$\alpha^* = \frac{-\alpha}{\ln!\left(1 - \frac{1}{k \cdot \ln(b)}\right)}$$

where $b$ is the logarithmic base (10 for HS/Koff equations). Standard errors are computed via the delta method. alpha_star requires $k \cdot \ln(b) > 1$; otherwise NA is returned.

tidy(fit_hs) |>
  filter(term == "alpha_star") |>
  select(id, term, estimate, std.error)

Plotting

Basic Demand Curves

The plot() method displays fitted demand curves with observed data points. The x-axis defaults to a log10 scale:

plot(fit_hs)

Faceted by Subject

Use facet = TRUE to show each subject in a separate panel:

plot(fit_hs, facet = TRUE)

Axis Transformations

Control the x- and y-axis transformations with x_trans and y_trans:

plot(fit_hs, x_trans = "pseudo_log", y_trans = "pseudo_log")

Diagnostics

Model Checks

check_demand_model() summarizes convergence, residual properties, and potential issues:

check_demand_model(fit_hs)

Residual Plots

plot_residuals() produces diagnostic plots. Use $fitted for a residuals-vs-fitted plot:

plot_residuals(fit_hs)$fitted

Predictions

Default Predictions

predict() with no arguments returns fitted values at the observed prices:

predict(fit_hs)

Custom Price Grid

Supply newdata to predict at specific prices:

new_prices <- data.frame(x = c(0, 0.5, 1, 2, 5, 10, 20))
predict(fit_hs, newdata = new_prices)

Aggregated Models

Instead of fitting each subject individually, you can fit a single curve to aggregated data.

Mean Aggregation

agg = "Mean" computes the mean consumption at each price across subjects, then fits a single curve to those means:

fit_mean <- fit_demand_fixed(apt, equation = "hs", k = 2, agg = "Mean")
fit_mean
plot(fit_mean)

Pooled Aggregation

agg = "Pooled" fits a single curve to all data points pooled together, retaining error around each observation but ignoring within-subject clustering:

fit_pooled <- fit_demand_fixed(apt, equation = "hs", k = 2, agg = "Pooled")
fit_pooled
plot(fit_pooled)

Conclusion

The fit_demand_fixed() interface provides a modern, consistent API for individual demand curve fitting with full support for the tidy() / glance() / augment() workflow. For datasets where borrowing strength across subjects is desirable, consider the mixed-effects or hurdle model approaches.

See Also



Try the beezdemand package in your browser

Any scripts or data that you put into this service are public.

beezdemand documentation built on March 3, 2026, 9:07 a.m.