First CRAN release. It is a complete redesign of the development snapshots 0.1.x and 0.2.0, which were distributed only through GitHub, and it is not backward compatible with them: function names, arguments, example datasets and returned objects changed. The Shiny explorers and the budget/KPI helpers of the earlier snapshots are not part of this package. This release contains the reach and exposure-distribution models, the fitting of the Beta-Binomial model to an observed reach, a scoped Negative-Binomial approximation and the evaluation of predicted distributions against observed ones.
fit_bbd_to_reach() now expands its log-concentration
search interval to bracket interior targets near the binomial or
polarized limit. Previously, valid targets outside the initial numerical
bracket could cause a raw uniroot() error. Regression
checks include an independent two-insertion identity, probability mass
and the mean exposure implied by the inputs. Exact boundary handling and
checks against the schedule bounds are retained.fit_bbd_to_reach() and the vignette
now name the two-step Hofmans Beta-Binomial (HBBD) procedure explicitly
and give a worked example: the reach of
calc_hofmans_duplication() is passed to
fit_bbd_to_reach() with one insertion per vehicle. There is
no single calc_hbbd() function, and the composition is not
validated against a published numeric example. A test checks that the
composition preserves the Hofmans reach and the mean exposure implied by
the audiences.Each correction is checked by
tests/testthat/test-audit-2026-10-12-followup.R, whose
witnesses (closed forms, a recurrence written in the test, base R
distributions) do not use the code under test. Results that were correct
do not change beyond rounding (about 1e-13 in a cell).
calc_csd() and calc_msad() (MP-01): the
reach of every marginal, of every step and of the result is now the sum
of the positive cells, not 1 - P(0), and the joint table of
a step is built from the probability of being reached by both parts, so
that a small audience combined with a large one keeps its digits. With
one insertion in each of two vehicles of audience
p = 1e-17, both models stopped
(missing value where TRUE/FALSE needed); for
p = 1e-16 they reported a reach of 2.22e-16
and an average frequency of 0.90, below one; for p = 1e-12
in a population of 1e12 they reported 1.99996 people
instead of 2. The average frequency is computed as one plus the excess
over one, which cannot fall below one. The Morgensztern reach forms the
ratio of reach to audience of each vehicle before the product, so that
it does not underflow. The collapse of the joint table by total
exposures is linear in its cells (it was quadratic).alpha + beta of
10 and by the recurrence of consecutive probabilities above it. Near the
independence limit the former difference of log-beta functions summed to
one only to about 1e-8 (a reach of 1.0000000081 for 500 insertions,
R1 = 0.3 and R2 at 2e-9 of the limit, and a
mean of 150.0000012 instead of 150), which also made
calc_csd() reject a valid plan (insertions 50 and 2,
R1 0.3 and 0.2, duplication 0.06). The mass is now one and
the mean is n * R1 to about 1e-13. The variance used by the
canonical expansion is written so that it does not overflow for a large
concentration. calc_canex() evaluates the marginals once
per vehicle, not once per exposure level.fit_bbd_to_reach() (MP-03): the binomial bound of the
feasible interval is -expm1(N * log1p(-mean)) times the
population; (1 - (1 - mean)^N) lost the whole bound for one
person in 1e17 and reported the interval
[1, 0]. The bounds are not widened by
precision.nbd_exposure_distribution() (MP-04, MP-07): the reach
for a size so small that mean_contacts / size
overflows (1e-308) is no longer one (it is
7.1e-306, as base R’s pnbinom() gives); the
variance does not overflow through mean_contacts^2
(mean_contacts = size = 1e200 gives 2e200),
and is Inf only when it exceeds the largest double. A
default table whose 0.999 quantile exceeds one million levels is refused
with an explanation, without the coercion warning to integer.calc_csd() and calc_cbd() (MP-05): the
canonical correlation terms are formed from the mean over the standard
deviation of each vehicle, so that two audiences of 1e-200
(a product of variances of 1e-400, below the smallest
double) are no longer rejected as NaN, and a pair of zero
correlation contributes nothing. Cells of the CBD grid whose product of
audiences is below the smallest double are evaluated on the log
scale.evaluate_exposure_model() (MP-06): a table with an
exposure level beyond the integer range, or with no rows, is rejected
with a message that names the table, the schedule and the level, without
a coercion warning and without the generic
'to' must be a finite number. Incomplete supports and
duplicated cells are still rejected, as before.calibrate_bbd() (MP-08): the binomial and polarized
limits of the family are evaluated as candidates for every number of
insertions of at least two, and are returned only if they improve the
error by more than 1e-12, so the results that the interval already
resolved do not change. A target equal to the binomial limit
(R1 = 0.3, target_reach = 0.51, two
insertions) is now reached exactly. The result has the new element
fit_type, the candidates the new column
fit_type, and the help page states the interval of log
concentrations searched.calc_canex() (MP-09): R2 may be
NA (or any value, which is ignored) for a vehicle with a
single insertion, as in the sequential models; it is still required and
validated for vehicles with two or more insertions. The distribution of
a vehicle with one insertion is a Bernoulli of probability
R1, as before.calc_csd(), calc_msad(),
calc_cbd() and calc_mbd() (MP-10): the size of
the largest joint table (CSD and MSAD) or the work (CBD and MBD) is
estimated from the insertions before anything is allocated, and a
schedule beyond the budget is refused with an explanation. The budgets
are the options mediaPlanR.max_table_cells (default
1e7) and mediaPlanR.max_work (default
1e9); see “Computational limits” in
?mediaPlanR.calc_canex() is a display equation. The final pass of the
regenerated manual has no overfull box (the earlier one had lines that
ran into the right margin on pages 3, 5, 9, 10, 34, 41, 45 and 46).tools/build-manual.R (MP-12) stops if
pdflatex or makeindex return a non-zero code,
no longer accepts a PDF left by an earlier pass, keeps a log of the
build and lists the overfull boxes.Each correction is checked by
tests/testthat/test-extremes-audit-2026-10-12.R. No result
that was correct changes.
nbd_exposure_distribution(): for a very large
size the quotient mean_contacts / size could
underflow to zero, and the reach was zero (and the average frequency
infinite) while the table kept the positive cells
(mean_contacts = 1e-17, size = 1e308). The
cumulant is now computed from its series when the quotient is below 1e-8
(error of order 1e-24).nbd_exposure_distribution(): opportunities
above the integer range (for instance 3e9) was turned into
NA, with its diagnostics; it is now kept as a number.
Values within the integer range are still integers.Each correction is checked by
tests/testthat/test-final-audit-2026-10-09.R, whose
witnesses are computed without the code under test.
nbd_exposure_distribution(): the reach is computed as
-expm1(-size * log1p(mean / size))
(-expm1(-mean) for the Poisson limit) instead of
1 - P(0). For a mean of 1e-17 the reach was zero and the
average frequency infinite, although the table kept the positive
probabilities. calc_cbd() and calc_mbd()
obtain their reach as the sum of the positive cells for the same
reason.calc_canex() may change the mean number of
exposures (it did not in a compatible symmetric example); the table of
reactions to warnings no longer says that it did.Documentation only: no calculation changes. Each statement added to
the help pages is checked by
tests/testthat/test-theory-audit-2026-10-09.R.
calc_hofmans_duplication() and the constant-coefficient
formula of calc_hofmans_accumulation() give the same reach
(new Details and example).calc_mbd() has a new section, “What the result
conserves”. The bounded conditional allocation of the published
procedure (Cheong, 2007, pp. 65-66) can change the marginals and the
mean number of exposures with no negative probability, so
diagnostics$mean_error must be read whether or not the
safety net was engaged, and the average frequency of the result cannot
be combined with the gross rating points of the inputs. The README no
longer says that MBD conserves the mean, and ?mediaPlanR
states which models conserve it (CSD and MSAD) and which can alter it
(CANEX, CBD and MBD).?mediaPlanR and the README).mediaPlanR_low_duplication says so, and
?calc_canex gives an example of valid data below the random
duplication that calc_canex() reproduces.calc_cbd(): in its general formulation CBD takes the
zero probability of the schedule from any existing model (Kim, 1994,
p. 124; Kim, 2005, p. 65), and this implementation takes it from the
canonical expansion; the pages of Kim (2005) are 59-65; the earlier
range quoted for the departure of the mean could not be reproduced and
is replaced by figures that can (csd_example and the
maintainer’s local runs). CBD is called a multivariate model of
conditional expansion, not a sequential-aggregation model (see also the
title of ?print_sequential and the seealso of
CSD and MSAD).?calc_canex (pairwise
terms, linear in the correlations), and the messages of CANEX, CSD and
CBD no longer use the phrase.R2 <= 2 * R1 - R1^2 is documented as the
bound of the Beta-Binomial family, not as the logical limit of two
insertions (R2 <= min(1, 2 * R1)), with an example of
possible data outside the family.evaluate_exposure_model(): the denominator of the APE
is the observed reach; it equals the sum of the observed positive cells
used by Kim (2005, p. 118) when the observed mass is one.tools/build-manual.R (not part of the archive) builds
the PDF manual from the help pages with a layout setting that keeps long
function names inside the right margin; the unmodified
R CMD Rd2pdf output has about 75 lines that run into
it.evaluate_exposure_model() accepts any observed reach
above the double-precision epsilon (it used to reject reaches below
about 1.5e-8 as zero); the limit is now stated in the help page and in
the error message.inst/DATA-PROVENANCE.md: the statement on
redistribution distinguishes the datasets and tables (not distributed)
from the individual printed values quoted next to the computed
differences.Validation:
fit_bbd_to_reach(): precision measures the
fit to the target and no longer widens the bounds of the schedule. A
reach below the largest audience is rejected whatever the precision; a
limit (polarized or binomial) is selected only if it lies inside the
bounds of the schedule; the fitted reach is checked against those
bounds, and the function stops when no Beta-Binomial with the mean of
the schedule is compatible with it. The reach is computed with a
numerically stable formula, and parameters$schedule_bounds
reports the bounds next to feasible_reach (the interval of
the family).Documentation:
calc_mbd() with the printed
three-vehicle example of Cheong is 0.0020348568 in probability, not
0.002 or 0.0019; the test threshold (0.0025) is distinguished from the
real maximum, the printed intermediate tables are described as a
plausible explanation and not as an established cause, and the maximum
of MSAD with Lee (1988) is 0.001077973. The cell-by-cell comparisons are
in inst/DATA-PROVENANCE.md.sum_tolerance of evaluate_exposure_model()
does not guarantee that a table is complete: it rejects total-mass
deficits above the threshold and cannot distinguish rounding from a
small omitted tail.DESCRIPTION states what the three doctoral
dissertations are and that they have no DOI; the Sainsbury help page
marks Caffyn and Sagovsky (1963) as a secondary citation.evaluate_exposure_model()
that overflowed the right margin of the PDF manual was split.Data:
csd_kim2005, msad_kim2005 and
mbd_cheong2007 were removed, because the basis for
redistributing the inputs of published examples was not established. The
examples and the vignette use csd_example,
msad_example and mbd_example; the comparisons
with the published examples are local-only tests of the maintainer.Validation:
fit_bbd_to_reach() separates two cases: a reach that is
logically impossible for the schedule (below the largest audience or
above its contacts) and a reach that is possible but that the
Beta-Binomial family cannot represent. Its precision
default is now NULL, meaning 1e-4 * population
(the former 100 people for a universe of one million, and a
scale-appropriate tolerance for small universes); the printed fit shows
the reaches with enough digits.evaluate_exposure_model() rejects any probability above
one (or percentage above 100) before applying the tolerance of the
total, and has a new argument censored_last_level for
tables whose last level is a collapsed tail: the exact mean and
mean_contact_bias are then NA, and the means
of the censored codes are reported separately.NA
in calc_canex(), instead of zero.Documentation:
calc_mbd() is described as a package
correction of the final distribution, not as Cheong’s MBD-ADJ, which
acts on the joint-exposure table before it is expanded; the study’s
accuracy figures are attributed to its own variants (MBD, MBD-ADJ,
MBD-ADJ2).alpha, beta of 0 and
Inf are indicators of a limit), the homogenized Binomial,
calibration as a search that can fail, the Negative-Binomial result and
evaluation result structures, the open-tail mean, the sample required by
fit_nbd_exposure(), normalization and the positive observed
reach of the evaluator, and the contract differences of
calc_canex() (R2 for every vehicle).?mediaPlanR.calc_mbd() is written as plain text
(doi:10.2753/...) instead of a
https://doi.org/ link: R CMD check --as-cran
asks for the \doi{} macro for links, but that macro drops
the hyphen of this particular DOI when the PDF manual is typeset.calc_beta_binomial() now takes the reach proportions
R1 and R2, the number of
insertions and the population (default 1),
instead of the audiences A1, A2 in people,
n and P. This matches
calc_hofmans_accumulation(), calc_canex() and
the sequential models.calc_hofmans_accumulation() takes
insertions instead of N, and its results table
names that column insertions.fit_bbd_to_reach() takes population
instead of universe (and reports it as
parameters$population); calibrate_bbd() takes
R1 instead of first_reach.vehicles_data of calc_canex() accepts
a column named insertions, as the sequential models do,
besides the original k.?mediaPlanR.calc_canex() now uses population = 1 by
default, like the sequential models and
calc_beta_binomial(), so that the people
columns equal the probabilities unless a population is supplied. The
printed reports leave out the number of people when the population is
1.R2, and a hint when values look like percentages instead of
proportions. The Metheringham error about the minimum number of
insertions is more explicit.?mediaPlanR has new “Assumptions of the models” and
“Notation and units” sections; ?calc_canex opens its
“Domain of validity” section in plain words, and the vignette adds a
table of what to do when a function stops or warns. The help pages state
the unit of every argument (people, proportion between 0 and 1) and that
aggregation_order does not affect
calc_cbd().calc_csd(),
calc_msad(), calc_cbd() and
calc_mbd() now show the same kind of report as the
classical models: headline metrics, parameters, aggregation steps,
exposure and cumulative distributions, and diagnostics.
max_rows cuts long distributions with a note, and
print(x, full = FALSE) gives the previous four-line
summary.DATA-PROVENANCE.md records
dataset/fixture sources, the redistribution basis and numerical
discrepancies. The two published-plan fixtures (whole numeric appendices
of Kim 2005 and Hong 1998) are excluded from the package archive; the
tests that read them are skipped when absent and the CI workflow runs
the complete suite from the source tree. HBBD/DMD descriptions no longer
claim that the available literature lacks their specifications.calc_sainsbury() (exact
Poisson-binomial distribution by dynamic convolution) and
calc_binomial(), both with an insertions
argument for several insertions per vehicle.calc_beta_binomial(),
with the binomial and polarized limits handled explicitly, and
calc_hofmans_accumulation() (Hofmans’ equations 3.11 and
3.12 in Aldás Manzano, 1998; an optional third observation
R3 estimates the variable-coefficient exponent).calc_agostini_duplication()
(R = A^2 / (A + k D) with Agostini’s
k = 1.125) and calc_hofmans_duplication()
(pairwise coefficients). Both take the duplication matrix and return
reach only.calc_metheringham() (a
Beta-Binomial for N = sum(insertions) insertions, from
audiences and duplications averaged over all pairs of insertions,
following Aldás Manzano, 1998, Section 3.3.1.5),
calc_canex(), calc_csd(),
calc_msad(), calc_cbd() and
calc_mbd().fit_bbd_to_reach() and calibrate_bbd() fit
a Beta-Binomial to an external reach or to a target effective
reach.nbd_exposure_distribution() and
fit_nbd_exposure() provide an explicitly scoped
Negative-Binomial approximation for unbounded exposure-count
processes.evaluate_exposure_model() compares predicted and
observed exposure distributions with declared scales and identical
supports, reproducing Kim’s
(2005) average error in reach and in the exposure distribution.NA, NaN, Inf, logical, factor and
list values, wrong types and wrong lengths with informative errors.calc_csd() reproduces the worked example of Kim (2005)
within the rounding of its published tables; calc_msad()
the numerical example of Lee (1988, pp. 81-93); calc_cbd()
the worked example of Kim (1994, p. 139) except for its zero cell (about
0.4 percentage points) and the CBD columns of Kim (2005, Appendix B) and
Hong (1998, Appendix E); and calc_mbd() the worked example
of Cheong (2007, p. 75; also Cheong, Leckenby and Eakin, 2011), with a
largest absolute difference of 0.0020348568 in probability.
calc_canex(), calc_csd() and
calc_msad() also reproduce the 40 plans of Kim (2005,
Appendix B) within 0.02 percentage points per cell (MSAD plan 19, 0.099
with the common reconstructed duplication; its printed row is reproduced
with random duplication). The three remaining differences with published
values (Kim 1994 zero cell, Cheong MBD, Kim 2005 MSAD plan 19) are
documented, with the evidence that bears on each one and explicit
regression bounds, in inst/DATA-PROVENANCE.md; the cause of
none of them is established beyond that evidence. The test suite checks
all of these.do.call(). The inputs of published worked examples are not
distributed.extraDistr and ggplot2 are suggested, not
imported: the Beta-Binomial probability function is computed internally
(and cross-checked against extraDistr in the tests), and
the plot of calc_hofmans_accumulation() is
NULL when ggplot2 is not installed.diagnostics; Danaher (1991) justifies this because that
mass is negligible, and the documentation says what happens to the mean
exposure when it is not. CSD, MSAD and CBD stop instead of altering an
invalid canonical target. MBD and CBD zero any negative cell that
remains and redistribute its mass proportionally (Cheong’s MBD-ADJ), and
calc_mbd() warns for four or more vehicles.calc_cbd() follows Kim’s (1994) specification: the
vehicles are conditionally independent given the (0,1) grid, the zero
cell is conformed to the canonical expansion of all the insertions, and
the result does not depend on aggregation_order. The
returned object reports the within-vehicle distribution used for each
vehicle in vehicle_expansion.mediaPlanR_low_duplication) when the canonical correlation
of a pair is below -0.1. calc_canex() warns (class
mediaPlanR_canex_truncation) when the truncated negative
mass exceeds 1e-4 and reports the expected and returned mean number of
exposures. Both thresholds are decisions of this package, not of the
cited authors.calc_mbd() and calc_cbd() support at most
12 vehicles because of the exponential cost of their exposure grid.