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 |
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:
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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
|
required |
vendor_type_col
|
str
|
Column identifying the vendor category or vendor type. Vendor counts are
calculated separately for each combination of |
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: |
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'
|
per_pop_col
|
str
|
Name of the output column containing vendors per person. The default is
|
'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'
|
rate_per
|
float | None
|
Optional population scaling factor used to create an easier-to-read
rate. For example, |
None
|
rate_col
|
str | None
|
Optional name of the scaled population density column. If not provided
and |
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 |
ValueError
|
If any value in |
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",
... )