TURF analysis (total unduplicated reach and frequency) with integer linear programming. This is the Python port of the turfLP R package. It uses the same algorithm, solver settings, limits, and warning texts, and it passes the same conformance suite (conformance/ in the repository), whose expected values come from exact enumeration of every portfolio.
The solver is HiGHS through highspy. The dependencies are highspy and NumPy.
pip install turfLPPython 3.10 or later. The package is turfLP on PyPI, and the module is turflp (import turflp). To install the development version from the repository:
pip install "git+https://github.com/aigorahub/turfLP.git#subdirectory=python"The input is a matrix with one row per respondent and one column per product: nested lists, a NumPy array, or a pandas data frame. A 1 (or True) means that the product reaches the respondent.
from turflp import turf, turf_sizes, datasets
ham = datasets.load("ham") # 127 consumers, 9-point liking of 8 hams
reach = ham.values >= 7 # top-3 box
print(turf(reach, 2, names=ham.columns))
# Portfolio(products=(3, 4), names=('N4', 'S1'), size=2, reach=98, reach_prop=0.7716535433070866,
# frequency=118, penetration=58.98305084745762, respondents=127)
for p in turf_sizes(reach, [1, 2, 3, 4], names=ham.columns):
print(p.size, p.reach, p.names)
# 1 60 ('S1',)
# 2 98 ('N4', 'S1')
# 3 111 ('N2', 'N3', 'S1')
# 4 119 ('N1', 'N3', 'N4', 'S1')turf(reach, size, tiebreak=("frequency", "penetration"), *,
names=None, max_pool=1000, max_pool_seconds=30.0) -> Portfolio
turf_min_cover(reach, *, names=None) -> Portfolio
turf_sizes(reach, sizes=None, tiebreak=("frequency", "penetration"), *,
names=None, max_pool=1000, max_pool_seconds=30.0) -> list[Portfolio]turffinds thesizeproducts that reach the most respondents. Ties on reach are broken bytiebreak, in order:"frequency"(the sum of the individual reaches) and"penetration"(the harmonic mean of the individual reaches). An emptytiebreakmeans reach only.turf_min_coverfinds the fewest products that together reach every respondent that some product reaches.turf_sizessolves several sizes. The default is every size from 1 to the minimum cover size.Portfoliois a frozen dataclass withproducts,names,size,reach,reach_prop,frequency,penetration, andrespondents.namesgives the product names; otherwise they come from the data frame columns, or areP1,P2, and so on.
The penetration tie-break is decided in Python, not by the solver: the package collects every portfolio whose penetration can equal the optimum and compares them with the rule in docs/algorithm.md section 5. After the first penetration solve, that search examines up to max_pool more portfolios (1000 by default) within max_pool_seconds seconds (30 by default). If it stops early and frequency comes next, the frequency stage gets another max_pool_seconds. When a search stops early, the package emits a turflp.TurfWarning that says which result is not proved optimal. max_pool must be a whole number of 0 or more and max_pool_seconds a number of 0 or more (math.inf for no limit); they are checked before any solve. The reach stage and the first penetration solve have no time limit.
- Product indices are 0-based (
products=(3, 4)above isc(4, 5)in R). turf_sizesreturns a list ofPortfoliovalues instead of a data frame.- Warnings are
TurfWarningwarnings with the same texts as the R warnings. - The limits are keyword arguments instead of the R options
turfLP.max_poolandturfLP.max_pool_seconds. - HiGHS presolve is on. The R package turns it off because HiGHS 1.14, which the R
highspackage bundles, returned a wrong optimum with presolve on in a test case. HiGHS 1.15 solves that case correctly. - There is no
turf_simulate().
When several portfolios are optimal, R, Python, and JavaScript can return different ones, with the same values of the requested criteria. When a search stops early, the warning says which value is not proved optimal.
turflp.datasets.load(name) returns a Dataset(columns, values) with the raw responses of one of the seven example data sets of the R package: cafe, chips, coffee, ham, icecream, lunchbags, and pies. ham, pies, and lunchbags are under CC BY 4.0 and coffee under CC0 1.0; see COPYRIGHTS.
MIT. See LICENSE. The data licenses are in COPYRIGHTS.