Skip to content

Heterogeneous household demand by utility and by elasticities

Hamill and Gilbert's heterogeneous households, whose demand for food traces a curve as its price rises.

  • Name: household_demand
  • Field: economics/markets
  • Formalism: discrete map
  • References: Hamill and Gilbert 2016

Select this system

from skcomplex.systems import create_simulation, create_system, describe

system = create_system('household_demand')
entry = describe('household_demand')
trajectory = create_simulation('household_demand').prepare().run().trajectory_

Factory

@register_system(
    field='economics/markets',
    tags=('demand', 'heterogeneity', 'agents', 'price elasticity', 'income distribution', 'discrete map'),
    references=('Hamill and Gilbert 2016',),
    defaults={'until': 10},
    observables=lambda system: [
        ObservableSpec('total_demand', group='households', variable='demand', reduce='sum'),
        ObservableSpec('total_expenditure', group='households', variable='expenditure', reduce='sum'),
        ObservableSpec('mean_utility', group='households', variable='utility', reduce='mean'),
    ],
)
def household_demand(
    n_households: int = 1000,
    *,
    demand: str = 'cobb-douglas',
    shares: Sequence[float] = FOOD_SHARES,
    share_deviation: float = 0.02,
    elasticities: Sequence[float] = FOOD_ELASTICITIES,
    elasticity_deviation: float = 0.1,
    minimum: float = 100 / 3,
    change: float = 0.1,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return Hamill and Gilbert's heterogeneous households, whose demand for food traces a curve as its price rises.

    Every household has a budget drawn from an exponential law shifted right by a minimum and normalized to a mean of
    100, which gives a Gini coefficient of about a third and a P90/P10 ratio just over 4, and a budget share of food
    drawn around the mean of its budget quintile, from 15 percent for the poorest to 8 percent for the richest. Every
    step the price rises by the share `change`, from 1. With the Cobb-Douglas utility of Section 3.2 of Hamill and
    Gilbert (2016) the demand is `alpha m / p`, the own-price elasticity is -1, the expenditure stays the same, and the
    poorest lose the most utility. With the practical model of Section 3.3 the households also have own-price
    elasticities drawn around the means of their quintiles, from -0.6 to -0.3, so a price rise of 10 percent lowers
    the demand by about 4.4 percent and raises the expenditure by about 5.1 percent, more for the richest.

    Args:
        n_households:
            The number of households.

        demand:
            `'cobb-douglas'` for the utility model, `'elasticity'` for the practical model.

        shares:
            The mean budget share of the good of every budget quintile, from the poorest.

        share_deviation:
            The standard deviation of the budget shares within a quintile.

        elasticities:
            The mean own-price elasticity of every budget quintile, from the poorest.

        elasticity_deviation:
            The standard deviation of the elasticities within a quintile.

        minimum:
            The shift of the exponential law of the budgets before normalization.

        change:
            The relative rise of the price every step.

        random_state:
            The seed or generator of the budgets, shares, and elasticities.

    Returns:
        system:
            The system, with the group `households` of the demands `households.demand`, the expenditures
            `households.expenditure`, the utilities `households.utility`, and the prices `households.price`. The
            observables of the system are `total_demand`, `total_expenditure`, and `mean_utility`.

    Examples:
        >>> from skcomplex.systems.economics.markets import household_demand
        >>> household_demand(10).elements[0].prepare().size_
        10
    """
    rng = np.random.default_rng(random_state)
    budgets = _budgets(n_households, minimum, rng)
    alphas = np.clip(_by_quintile(budgets, shares, share_deviation, rng), 0.0, 1.0)
    slopes = _by_quintile(budgets, elasticities, elasticity_deviation, rng) if demand == 'elasticity' else -1.0
    quantities = alphas * budgets
    households = GroupElement(
        'households',
        element=BudgetHouseholdElement,
        size=n_households,
        values={
            'budget': budgets,
            'alpha': alphas,
            'elasticity': slopes,
            'demand': quantities,
            'expenditure': quantities,
            'utility': quantities**alphas * (budgets - quantities) ** (1 - alphas),
            'change': change,
            'kind': demand,
        },
    )
    return System(elements=[households])

Components

Elements:

  • BudgetHouseholdElement, from Element
class BudgetHouseholdElement(Element):
    """Household with a budget that buys a good at a price that rises by a share every step.

    With the demand `'cobb-douglas'` the household maximizes `A^alpha B^(1 - alpha)` against a composite good `B` of
    price 1, so it buys `A = alpha m / p` and spends the share `alpha` of its budget `m` on the good. With the demand
    `'elasticity'` it spends the budget share `alpha` at the initial price `p0` and its demand changes with its
    own-price elasticity, `A = (alpha m / p0) (p / p0)^elasticity`.

    Args:
        label:
            The label of the agent or group.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        demand:
            The demand for the good.

        expenditure:
            The expenditure on the good.

        utility:
            The Cobb-Douglas utility of the good and the composite good bought.

        price:
            The price of the good.

        budget:
            The budget of the household.

        alpha:
            The budget share of the good, the exponent of the Cobb-Douglas utility.

        elasticity:
            The own-price elasticity of the demand.

        change:
            The relative rise of the price every step.

        initial_price:
            The price at the start.

        kind:
            The demand, `'cobb-douglas'` or `'elasticity'`.

    Raises:
        ValidationError:
            The demand is unknown, raised by `prepare`.

    Examples:
        >>> from skcomplex.core.components.elements import GroupElement
        >>> from skcomplex.systems.economics.markets import BudgetHouseholdElement
        >>> GroupElement('households', element=BudgetHouseholdElement, size=2, values={'budget': [50.0, 150.0]}).size
        2
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        demand: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        expenditure: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        utility: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        price: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
        budget: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 100.0,
        alpha: Annotated[float, StateSpec(domain=RealSet.closed(0, 1))] = 0.1,
        elasticity: Annotated[float, StateSpec()] = -1.0,
        change: Annotated[float, ParameterSpec(domain=RealSet.open(-1, oo))] = 0.1,
        initial_price: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        kind: str = 'cobb-douglas',
    ) -> None:
        super().__init__(label, visual=visual)
        self.demand = demand
        self.expenditure = expenditure
        self.utility = utility
        self.price = price
        self.budget = budget
        self.alpha = alpha
        self.elasticity = elasticity
        self.change = change
        self.initial_price = initial_price
        self.kind = kind

    def _check(self) -> None:
        """Raise an error for an unknown demand.

        Raises:
            ValidationError:
                The demand is unknown.
        """
        super()._check()
        if self.kind not in DEMANDS:
            error_msg = f'The demand of {self.label!r} is {self.kind!r}. Use one of {DEMANDS}.'
            raise ValidationError(error_msg)

    def get_demand(self, s: SimpleNamespace, price: Expression) -> Expression:
        """Return the demand of the household at a price.

        Args:
            s:
                The namespace of the household.

            price:
                The price.

        Returns:
            demand:
                The demand for the good.
        """
        if self.kind == 'cobb-douglas':
            return s.alpha * s.budget / price
        return s.alpha * s.budget / s.initial_price * (price / s.initial_price) ** s.elasticity

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the rise of the price and the demand, expenditure, and utility at the new price.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The laws of the element.
        """
        price = s.price * (1 + s.change)
        demand = self.get_demand(s, price)
        rest = s.budget - price * demand
        return [
            UpdateLaw(s.price, price),
            UpdateLaw(s.demand, demand),
            UpdateLaw(s.expenditure, price * demand),
            UpdateLaw(s.utility, demand**s.alpha * rest ** (1 - s.alpha)),
        ]

Equations

households.demand[i]: demand_households(t + 1) == alpha_households(t)*budget_households(t)/((change_households + 1)*price_households(t))
households.expenditure[i]: expenditure_households(t + 1) == alpha_households(t)*budget_households(t)
households.utility[i]: utility_households(t + 1) == (-alpha_households(t)*budget_households(t) + budget_households(t))^(-alpha_households(t) + 1)*(alpha_households(t)*budget_households(t)/((change_households + 1)*price_households(t)))^alpha_households(t)
households.price[i]: price_households(t + 1) == (change_households + 1)*price_households(t)
households.budget[i]: budget_households(t + 1) == budget_households(t)
households.alpha[i]: alpha_households(t + 1) == alpha_households(t)
households.elasticity[i]: elasticity_households(t + 1) == elasticity_households(t)