Skip to content

Density

The density module provides functions for constructing vendor density and distribution indicators used in the Nutrition-Sensitive Food Environment Index (NFEI).

In food environment analysis, vendor presence is not only about the total number of vendors observed. It also matters how vendor counts relate to the population served and the size of the mapped area. A location with many vendors may still be underserved if the population is large, while a smaller location may appear well supplied if vendor availability is high relative to its population or land area.

This module supports two related measurement needs:

  • Population-based vendor density, which relates vendor counts to the population denominator of the mapped area.
  • Area-based vendor distribution, which relates vendor counts to land area in square kilometres.

The module also includes a helper function for estimating the population covered by a circular mapped area. This is particularly useful for centred food environment surveys, where vendors are mapped within a fixed radius but the population denominator must be estimated from a larger administrative area.

Together, these functions help users construct interpretable density and distribution indicators that complement food diversity, availability, and spatial exposure measures within the NFEI framework.

Estimate population coverage for a circular mapped area

estimate_population_from_radius(population: float, land_area_sqkm: float, radius_km: float, round_area: int | None = 2, round_population: int | None = 0) -> dict[str, float]

This function estimates the population covered by a radius-based survey area. It supports NFEI workflows where vendor mapping is conducted using a centred approach, such as mapping all vendors within a fixed radius around a market, neighbourhood centre, or other reference point.

The function assumes that population is evenly distributed across the larger known administrative area. It then estimates the population within the circular mapped area using the radius supplied by the user.

Parameters:

Name Type Description Default
population float

Total population of the larger administrative area used as the denominator. For example, this may be the population of a city, municipality, arrondissement, ward, or subcounty.

required
land_area_sqkm float

Total land area, in square kilometres, of the same administrative area represented by population.

required
radius_km float

Radius, in kilometres, of the mapped survey area.

required
round_area int | None

Number of decimal places used to round the estimated mapped area. If None, no rounding is applied. The default is 2.

2
round_population int | None

Number of decimal places used to round the estimated population. If None, no rounding is applied. The default is 0.

0

Returns:

Type Description
dict[str, float]

Dictionary containing:

  • "population_density_per_sqkm": population divided by land area.
  • "area_covered_sqkm": estimated circular mapped area.
  • "estimated_population": estimated population within the mapped radius.

Raises:

Type Description
ValueError

If population, land_area_sqkm, or radius_km is less than or equal to zero.

Notes

The estimated mapped area is calculated as:

pi * radius_km^2

The estimated population is calculated as:

(population / land_area_sqkm) * area_covered_sqkm

This is an approximation. It should be interpreted as an estimated denominator, especially in settings where population is not evenly distributed across the administrative area.

Examples:

Estimate the population covered by a 1 km radius survey area:

>>> import nfei
>>>
>>> result = nfei.estimate_population_from_radius(
...     population=738444,
...     land_area_sqkm=79,
...     radius_km=1,
... )

Return unrounded estimates:

>>> result = nfei.estimate_population_from_radius(
...     population=738444,
...     land_area_sqkm=79,
...     radius_km=1,
...     round_area=None,
...     round_population=None,
... )

Add vendor density and distribution indicators

add_vendor_density(df: DataFrame, group_col: str, vendor_type_col: str, population_col: str, land_area_col: str, count_col: str = 'vendor_type_pop', per_pop_col: str = 'vendor_type_per_pop', per_sqkm_col: str = 'vendor_type_per_sqkm', rate_per: float | None = None, rate_col: str | None = None) -> pd.DataFrame

This function computes vendor density indicators used in the NFEI workflow. It counts vendors by area and vendor type, then relates those counts to population and land area denominators.

The function produces two core indicators:

  • vendors per population, reflecting vendor availability relative to the number of people living in the area.
  • vendors per square kilometre, reflecting the spatial distribution or concentration of vendors across the mapped area.

These indicators help describe whether a food environment may be under-served, over-concentrated, or spatially dense relative to the population and land area being assessed.

Parameters:

Name Type Description Default
df DataFrame

Input dataframe. Each row should represent a vendor or vendor observation.

required
group_col str

Column identifying the geographic unit, mapped area, or administrative unit within which vendor counts should be computed. Examples include "ward", "subcounty", "community", or "survey_area".

required
vendor_type_col str

Column identifying the vendor category or vendor type. Vendor counts are calculated separately for each combination of group_col and vendor_type_col.

required
population_col str

Column containing the population denominator for each group. This can be a known administrative population or an estimated mapped-area population from :func:estimate_population_from_radius.

required
land_area_col str

Column containing land area in square kilometres for each group. This can be a known administrative land area or an estimated mapped-area coverage.

required
count_col str

Name of the output column containing the vendor count by group and vendor type. The default is "vendor_type_pop".

'vendor_type_pop'
per_pop_col str

Name of the output column containing vendors per person. The default is "vendor_type_per_pop".

'vendor_type_per_pop'
per_sqkm_col str

Name of the output column containing vendors per square kilometre. The default is "vendor_type_per_sqkm".

'vendor_type_per_sqkm'
rate_per float | None

Optional population scaling factor used to create an easier-to-read rate. For example, rate_per=1000 creates vendors per 1,000 people. If None, no scaled rate column is created.

None
rate_col str | None

Optional name of the scaled population density column. If not provided and rate_per is used, the column name is generated automatically from per_pop_col and rate_per.

None

Returns:

Type Description
DataFrame

Copy of the dataframe with vendor count, vendors per population, vendors per square kilometre, and optionally a scaled population-rate column added.

Raises:

Type Description
KeyError

If any of group_col, vendor_type_col, population_col, or land_area_col is not found in the dataframe.

ValueError

If any value in population_col or land_area_col is less than or equal to zero, or if rate_per is provided and is less than or equal to zero.

Notes

Vendor counts are calculated for each unique combination of geographic group and vendor type. The resulting count is merged back onto the original dataframe, so each vendor row receives the count and density values for its own group and vendor type.

The main calculations are:

vendor_type_per_pop = vendor_type_count / population

vendor_type_per_sqkm = vendor_type_count / land_area_sqkm

If rate_per is provided, the scaled rate is calculated as:

vendor_type_per_pop * rate_per

Examples:

Compute vendor density using known population and land-area denominators:

>>> import pandas as pd
>>> import nfei
>>>
>>> df = pd.DataFrame(
...     {
...         "ward": ["A", "A", "A", "B"],
...         "vendor_type": ["shop", "shop", "kiosk", "shop"],
...         "Population": [1000, 1000, 1000, 800],
...         "Land_area_sqkm": [2.0, 2.0, 2.0, 1.5],
...     }
... )
>>> result = nfei.add_vendor_density(
...     df,
...     group_col="ward",
...     vendor_type_col="vendor_type",
...     population_col="Population",
...     land_area_col="Land_area_sqkm",
... )

Add an interpretable scaled rate, such as vendors per 1,000 people:

>>> result = nfei.add_vendor_density(
...     df,
...     group_col="ward",
...     vendor_type_col="vendor_type",
...     population_col="Population",
...     land_area_col="Land_area_sqkm",
...     rate_per=1000,
... )

Use custom output column names:

>>> result = nfei.add_vendor_density(
...     df,
...     group_col="ward",
...     vendor_type_col="vendor_type",
...     population_col="Population",
...     land_area_col="Land_area_sqkm",
...     count_col="vendor_count",
...     per_pop_col="vendors_per_person",
...     per_sqkm_col="vendors_per_sqkm",
...     rate_per=1000,
...     rate_col="vendors_per_1000_people",
... )