Fixed the shrinking method depending on the outcome’s units when the outcome is small, such as a rare binary outcome or a continuous outcome in large units. Its search stopped as soon as the outcome came within an absolute 1e-6 of the goal. It now stops within 1e-6 relative to the goal below 1.
The default cubic cost now handles wide bounds, such as 0 to 5000, which failed with a singular system. A badly conditioned solve is redone in each component’s share of its range. Narrow bounds, such as 1 to 1.00001 or 10 to 10.0001, could give a cost curve whose average unit cost was far off, by 27% and 15% in these examples, and the default cubic is now checked against an accurate solve and made flatter until it matches. A curve the plain solve gave within 0.1% is unchanged.
Fixed the numerical optimizer refusing a reachable goal when all its diagonal restarts failed, before it tried the other starts and the point of the highest outcome.
An unreachable power goal now always uses the shrinking method,
and shrinks to the stage-1 intervention on both links. On the identity
link the model can predict the goal of 1 it is set to, so it used to
optimize to that goal, and lago_budget() counted it as
reached. lago_budget() now never counts a shrunk run as
reached. The power goal warning now says the recommendation is shrunk,
to the stage-1 intervention or to
prev_recommended_interventions when given, and the run no
longer also warns that the maximum achievable outcome is below the
goal.
Fixed a binary lago_budget() for a rare outcome when
maximizing, or for an outcome near 1 when minimizing. Its goal grid
starts at 0.02 and ends at 0.98, so a budget that only affords goals
beyond it was reported as infeasible. The grid now also fills that
gap.
lago_budget() now treats estimated outcomes within
1e-6 of each other as tied. Goals below the no intervention outcome all
give the same intervention, and on some platforms the solver noise
between them was above the old tolerance, so it could refine a gap far
below the best affordable goal and leave budget unspent.
Fixed visualize_cost() replacing an exact cubic
coefficient with the slider’s rounded value after the slider’s range was
narrowed.
print(), summary() and
lago_report() now show values closer to 0 than 0.1 with 4
significant digits rather than 4 decimals. This covers the estimate, the
interval bounds, the recommended intervention, the effective goal and
the test statistic. Values closer to 0 than 1e-6 still show as
0.
A covariate that glm() could not estimate is no
longer also reported as held at 0, and a factor covariate with only some
levels that glm() could not estimate is no longer reported
as dropped. get_confidence_set() now refuses, with a clear
message, a model with any other coefficient glm() could not
estimate, instead of returning an empty set.
Center level data now keeps an explicit
include_center_effects = FALSE. It used to fit center
effects anyway with no message, since the argument could not tell FALSE
from not given. Its default is now NULL, which means TRUE
for center level data and FALSE otherwise. Center level data also
refuses an outcome_name other than
"proportion", the column it is fitted on, instead of
fitting proportion while other parts read the named
column.
Fixed the shrinking method for center level data and weighted
data. The stage-1 intervention it shrinks towards was the plain mean of
the rows, so an unreachable goal gave a different recommendation than
the same patients one row each. It is now weighted by
center_sample_size, or by the glm()
weights.
The confidence set grid now includes each upper bound, as the
optimizer grid already did. When the step did not divide the range, the
set could miss the corner where the recommendation sits and come back
empty. confidence_set_grid_step_size must now be finite and
positive.
Fixed the numerical optimizer giving a costlier recommendation when an intervention component is in large units. A restart that ended a hair outside a bound was left out, so a point on the bound was lost, and the solver’s fixed tolerances could stop a wide component early. A restart within 1e-8 of a bound now counts as inside it, and a last start is solved in each component’s share of its range. Results that were already right are unchanged.
Fixed the unconditional power goal being held above the stage-1 control rate, which overstated it when the planned control rate, for example at low center characteristic values, was lower.
Overriding unit_costs,
default_cost_fxn_type or unit_cost_doses in
lago_sensitivity() or lago_budget() on a
stored result now replaces the stored cost_list_of_vectors.
The stored list used to stay in place and win.
Fixed binary lago_budget() never trying goals near 0
or 1, so a rare outcome could get a much worse result than the budget
buys. The goal grid now extends towards 0 and 1 without passing
them.
The default cubic cost no longer fails for a component held at one dose (equal bounds), which now gets the linear cost, or for narrow bounds far from 0, such as 1000 to 1100. Bounds so far from 0 for their width that the cubic cannot be computed accurately are refused with a clear message.
The printed cost IQR keeps 3 significant digits for a cost below 1, so a cost in small units no longer shows as 0 - 0.
A continuous outcome with a family other than gaussian on the
identity link, such as quasibinomial, now uses the variance
glm() reports, which weighs the rows by the family’s
variance function. Its intervals used to ignore that function.
The MCP server’s optimize and
sensitivity tools now take the next-stage size,
power_goal_approach, icc and
power_goal_cluster_id, so a power goal can be run through
them.
Fixed the unconditional power goal counting significance in the harmful direction. Its test statistic was squared, so an outcome at which the next stage would show that the intervention is worse also met the power goal. When the treatment arm started below control it could return such a goal, which has almost no power to show benefit, or NA. Only outcomes that show benefit now count.
Fixed a conditional power goal that cannot be reached silently
becoming an outcome goal of 0, which recommended no intervention. Both
approaches now warn that the power goal cannot be reached and set the
power-implied goal to 1, so the shrinking method is used. The warning no
longer blames icc when icc is 0 or not
given.
Fixed lago_budget() with a power goal counting a run
as reached when it was shrunk because the power-implied goal could not
be reached. It then called a shrunk point the maximum reachable outcome.
Such runs are now not reached, and the warning names the power
goal.
Fixed the shrinking method losing precision when an intervention component is in large units. Its search for the needed coefficient stopped at an absolute width of 1e-6, so a small coefficient was found too coarsely, and at very large units the recommendation fell back to the stage-1 values. The width is now relative to the coefficient below 1, so other results are unchanged.
The interval bounds of the confidence set and of the estimated outcome now keep 4 significant digits below 0.1 rather than 3 decimals. A rare binary outcome showed intervals such as 0.002 to 0.003, and some confidence set rows showed an interval that did not contain the goal although the row was in the set. Bounds of 0.1 or more are unchanged.
Fixed the binary confidence set running out of memory with four intervention components on the default grid, since it built a matrix with one row and one column per grid point to read its diagonal.
glm_family = "quasibinomial" now takes the logit
link by default. It used to fail with an unclear error unless
link was given.
Column names that are not valid R names, such as names with spaces, are now refused with a clear message. They used to fail with a parse error, or could fit a different model.
Fixed visualize_cost() losing the small high order
coefficients of a cubic cost. Its sliders report values rounded to 5
decimals, so the curve it drew and the cost list it returned and copied
could cost half of the curve it set up. The app now keeps the exact
values it sets, for the drawn curve too, and the copied text keeps full
precision.
lago_optimization() now warns when both
center_weights_for_outcome_goal and
center_effects_optimization_values are given, since the
center weights are used and the named center is ignored. It also warns
that weights is ignored for center level data, whose rows
are weighted by center_sample_size.
The check that the outcome goal is above or below the observed
mean now weighs the rows as glm() does, so for center level
data it uses the mean over patients rather than over rows.
lago_sensitivity() now refuses an intervention
component named like one of its result columns (value,
rec_int_cost, est_outcome_goal,
status), which used to overwrite or hide it.
lago_budget() likewise refuses a component named like a
column it uses in its search (outcome_goal,
est_outcome, reached, affordable
and the others), which used to change its result. The printed summary of
lago_sensitivity() now pairs each cost with its own value,
so a descending sweep is no longer described backwards, and its
baseline attribute is found for a value built with
seq().
When the outcome goal cannot be reached, print(),
summary(), plot() and
lago_report() now say that the shrinking method was used
and no confidence set is computed, rather than telling the user to set
include_confidence_set = TRUE. The result has a new field
shrinking_method_used. print() of a
lago_budget() result now says minimum for a minimize budget
that does not bind.
get_confidence_set() now accepts a single clustering
vector as cluster_id and refuses more than two with a clear
message.
With unit_cost_doses, the pinned cubic cost now also
does not depend on the units of the dose.
New argument unit_cost_doses in
lago_optimization(): the dose each unit cost in
unit_costs was measured at. The default cubic cost is then
pinned there, so the cost per unit at that dose is the measured unit
cost. Without it the cubic cost only matches each unit cost on average
over the bounds, which could be far off at the measured dose, and a
warning now says so. The cost is otherwise unchanged (#47).
Fixed the numerical optimizer returning a recommendation that
misses the outcome goal. NlcOptim::solnl() can stop at a
point short of the goal without an error, and that point was kept when
it was the cheapest. In one example with interacting components it
recommended a point whose estimated outcome was 4.92 against a goal of
9.5. A restart that does not reach the goal is now left out. The
optimizer also starts from the point of the highest outcome, and uses
that point itself when no restart reaches the goal.
The numerical optimizer now also starts off the diagonal of the bounds, at the corners and at each component’s midpoint. All its restarts started on that diagonal, so a near symmetric problem could stop at a costlier point, or at a saddle point that wrongly made a reachable goal look out of reach. An extra start replaces the answer only when it is clearly cheaper, so other results are unchanged.
Fixed the default cubic cost depending on the units of
unit_costs. Its slope was a fixed number rather than one in
the units of the cost, so restating the costs in thousands changed the
shape of the cost curve and the recommendation. The slope now scales
with each unit cost, so a unit cost of 1 is unchanged. Results with the
cubic cost and other unit costs should be recomputed. A unit cost of 0
no longer makes the cost calculation run forever, and a negative one is
refused.
The cost column of the confidence set now keeps 6
significant digits when that is more precise than 2 decimals, so a cost
in small units keeps its precision. Costs of 10000 or more are
unchanged, and the printed cost IQR still shows 2 decimals.
Fixed the overall intervention test on center level data. It
compared the mean of the row proportions and counted each row as one
patient. It now counts each row’s center_sample_size
patients, so it is the same test as for the same data at the patient
level. In one example its sign was wrong.
Fixed the confidence set when an additional covariate has a
coefficient that glm() cannot estimate, such as one that is
constant within each center. The confidence set was empty for a binary
outcome and failed for a continuous one. It is now the confidence set of
the fit without that term, which the run already warned it would
be.
A center or period whose rows all have a glm weight of 0 is now left out of the outcome model with a warning. It used to stop the run with a message about collinear predictors.
A factor or character outcome, and an intervention component that is not numeric, are now refused with a clear message. They used to fail with an unclear error, or with a wrong message about collinear predictors.
Fixed the confidence interval and the confidence set of a continuous outcome with fixed center or time effects. The variance was clustered on the centers and periods that the fixed effects already model, so with few centers the interval was far too narrow and the confidence set often empty, and with both effects the variance could be negative, so no interval was reported. In one example on the logit link the interval was 0.599 to 0.601 with an empty confidence set, and it is now 0.594 to 0.606. The variance is now the one used without fixed effects. Results without fixed effects, and all binary results, are unchanged. Continuous results with fixed center or time effects should be recomputed (#31).
The coefficient table of print(),
summary() and lago_report() now shows each
estimate with the standard error the intervals use, and says which one
it is. It used to show the glm() summary, whose standard
errors for a continuous outcome on the logit link, or with fixed
effects, were not the ones the intervals used (#30).
Fixed lago_optimization(), print(),
summary() and lago_report() failing with
“subscript out of bounds” for a binary or continuous logit outcome with
fixed center effects and more than one center weight, which includes the
default weights. The interval of the weighted average center was correct
but lost its lower and upper names, which the
printed output and the Python wrapper read.
print(), summary(), plot()
and lago_report() now label the interval and the confidence
set with the level they were computed at, from
confidence_set_alpha. They always said 95%, so an 80%
interval was printed as a 95% one.
Fixed three ways lago_budget() could miss the best
affordable outcome. When several goals tied on the estimated outcome, as
all the goals below the no intervention outcome do, it refined the gap
above the lowest of them rather than the highest, leaving budget
unspent. It stopped extending its goal grid after 6 extensions. So when
the best affordable outcome was far above the observed range, it was not
found, and the budget was still reported as not binding. It now extends
up to 30 times and warns if that is not enough. With
n_grid = 2 its refinement pass re-ran the same two goals,
so nothing was refined, and it now tries goals strictly inside the gap.
Other results of lago_budget() can also change slightly,
within the resolution of its goal grid.
Fixed the default center weights for center level data using only
each center’s first row of center_sample_size. With several
rows per center of different sizes, such as one row per center and
period, the weighted average center then depended on the order of the
rows and did not match the same data at the patient level. Each center
is now weighted by the sum of its center_sample_size, which
is how glm() weights its rows. Results for center level
data with several rows per center and the default weights should be
recomputed.
Fixed grid search never trying an upper bound that its step does not reach, which the default steps (a twentieth of the observed range) often do not reach when the bounds differ from the observed range. It then returned a costlier recommendation, or said the goal could not be reached and shrank, when the upper bound would have reached it. The grid of each component now includes its upper bound.
Fixed the power goal using the control rate at center
characteristic value 0, the reference center and the reference period.
The next-stage control arm gets no intervention, and its rate was taken
as expit() of the model intercept, which is the rate only
on the logit link when the model has no other terms. On the identity
link the intercept is already the rate, so expit() of it
was wrong even without other terms. With center characteristics or fixed
center effects, the power-implied outcome and the recommendation then
changed when a characteristic was merely shifted by a constant or the
reference center was changed. In one example the raw characteristic gave
a power-implied outcome of 0.294, whose true power is 0.215 against a
goal of 0.8, while the centered one gave the correct 0.467. The control
rate is now the estimated outcome with no intervention at the same
planning values as the outcome goal: the center characteristic values,
the center weights or named center, and the period. Results with a power
goal and center characteristics, fixed center effects or fixed time
effects, or on the identity link, should be recomputed.
Fixed the power goal on center level data counting each row as
one patient and summing the proportions as the number of events. It now
counts each row’s center_sample_size patients and its
events, as for the same data at the patient level, including the stage-1
cluster sizes used with icc. A row without
center_sample_size is left out, as glm()
leaves it out. In one example the power-implied outcome was 0.399, with
a true power of 0.889 against a goal of 0.8, and is now 0.343, the same
as for the patient level data.
Fixed the confidence interval and the confidence set of the
weighted average center on the logit link. The reported outcome is the
weighted mean of each center’s probability, but its interval was built
for expit() of the weighted mean linear predictor, a
different quantity, so the printed interval could exclude its own
estimate, for example an estimated outcome of 0.75 with an interval of
0.764 to 0.835. The interval is now the delta method interval of the
weighted mean probability, for binary and continuous outcomes and for
individual and center level data. A single named center, and the
identity link, are unchanged. Confidence sets for the weighted average
center on the logit link should be recomputed.
Fixed three errors in the shrinking method used when the outcome
goal cannot be reached. With interaction terms it read each main
component’s coefficient by its position in main_components,
so listing an interaction term before a main component, or the main
components in another order than in
intervention_components, gave a different recommendation
for the same model. It also searched for the needed coefficient only up
to 100, so an outcome on a large scale, such as a weight in grams, gave
a different recommendation than the same outcome in kilograms. When a
step of the search landed on the goal exactly, the search also returned
the lower end of its bracket instead of that step, which could be far
from the needed coefficient. Results that used the shrinking method
should be recomputed, most of all those with interaction terms or with a
needed coefficient above 100.
The documentation of shrinkage_threshold now says
that it is not used. The shrinking method is applied whenever the
outcome goal is not estimated to be reachable, which has been the
behavior since the threshold rule was removed.
Fixed the in-browser playground treating a numeric
NaN period as a missing value when it checks which centers
have a row the outcome model can use. The package turns a numeric
period column into a factor, which keeps NaN
as a level and its rows in the fit, so with fixed center and time
effects the playground could refuse to run, or say a center could not be
optimized for, when the package would fit it. When period
is used as a numeric intervention component or covariate instead, a
NaN still counts as missing, as it does in
glm().
Three input problems now get clearer refusals.
center_effects_optimization_values = NA_character_ with a
center column that has missing values but no explicit
NA level failed with “values in
center_weights_for_outcome_goal must sum up to 1”, and now says that
NA_character_ is not one of the centers and how to make the
missing centers an explicit level with addNA(). The three
refusals of center_effects_optimization_values no longer
print a line break and a run of spaces in the middle of the sentence,
and the one for an unknown center names the value. A global
options(na.action = "na.pass") or "na.fail"
with a missing value in a model variable or its glm weight failed inside
glm() with an error that did not name the option, and is
now refused up front with a message that says to use
"na.omit" or "na.exclude". Data without
missing values still runs under either option.
Fixed wrong results when two coefficients of the outcome model
got the same name. glm() names a factor coefficient by
joining the column name and the level, so a level labeled with the text
"NA" and an explicit NA level (as from
addNA()) both became, for example, centerNA,
and a factor visit with level 2 and a numeric
column visit2 both became visit2. The package
reads coefficients by name. With fixed center effects and
include_confidence_set = FALSE, the recommendation was then
computed for the wrong center, or with the center weights paired with
the wrong centers, without any error. With the confidence set, the call
failed with a message about unmatched coefficients. Such a fit is now
refused with a message that names the coefficient and its columns. This
also refuses some fits that gave correct results before, such as a
period or covariate with both kinds of NA level and no
confidence set, so that one rule applies everywhere. Center results from
such data computed without the confidence set should be recomputed after
renaming one of the levels or columns. When every run of a sweep fails,
such as with this refusal or an outcome model that cannot be fitted,
lago_budget() still returns feasible = FALSE,
but its warning and printed result now give the error of the first
failed run, and the new run_error field holds it. The
printed result no longer suggests a larger budget in that case.
The documentation of get_confidence_set() now says
that center_weights_for_outcome_goal has one weight per
center of the fitted model, in the order of its center levels, and the
documentation of center_effects_optimization_values now
says that NA_character_ selects an explicit NA
center level. The README examples now show the outcome model’s actual
printed call.
lago_sensitivity() and lago_budget()
are faster. No input they can sweep changes the outcome model, but every
run refitted it, which was most of the time of a sweep. A sweep now fits
the model once and reuses it for every run with identical fitting
inputs, and a budget search shares one fit across all of its sweeps. A
25 value sensitivity sweep on BB_data went from 3.7 to 1.3
seconds, and a default budget search from 6.6 to 2.3 seconds. Results
are unchanged, and the warnings and messages from the fit are still
raised for every run. A single lago_optimization() call
always fits its own model.
lago_sensitivity() and lago_budget()
now show each message about how the inputs were read once per call,
instead of once for every run of the sweep. Examples are the note that
the center column was converted to a factor and the note
that optimization_method was switched to grid search. A
message is hidden only when its text is exactly the same as one already
shown. Other messages, such as a note about one run’s power goal, and
the progress lines shown with quiet = FALSE still appear
for every run. The note that default_cost_fxn_type is
ignored when cost_list_of_vectors is given is now sent as a
message like the other input notes, rather than printed with
print(), so it no longer shows as [1] "..." in
the output.
Fixed two unclear refusals for fixed center and time effects. An
all NA center column was refused with “values
in center_weights_for_outcome_goal must sum up to 1”, or with a message
about the length of center_weights_for_outcome_goal when
the weights were given. It is now refused with the same kind of message
an all NA period column gets. A center or
period whose rows all have zero weight counted as usable, so when no
other center or period was left the fit failed with a rank deficiency
message that blamed collinear predictors. Such a level no longer counts,
and the message names the center or period that still has a positively
weighted row, if there is one.
get_confidence_set() now refuses a fitted model with
a factor, character or logical predictor that is not treatment coded
with its first level as the reference and each coefficient named after
its level. Examples are a model fitted with contr.sum,
contr.SAS, a global options(contrasts = ...)
that sets another coding, the default coding of an ordered factor, or a
treatment matrix such as contr.treatment(3) whose columns
are not named after the levels. The function reads every such predictor
that way. For a binary outcome it returned a wrong interval and
confidence set without any error. A treatment matrix whose columns are
not named after the levels gave a wrong result only for the period,
whose dummy is found by its level name, and is now refused for every
column so that one rule applies. For a continuous outcome it usually
failed with a message about unmatched columns, but it could also return
a wrong interval without any error, for example for a factor with levels
0 and 1. The new message names each such column and shows how to refit
the model. Models fitted by lago_optimization() already use
this coding, so its results are unchanged. Results from direct calls
with such a model should be recomputed after refitting it.
Fixed a data column named weights replacing the
weights of the outcome model. glm() looked up its
weights argument in the data first, so such a column
replaced the weights argument of
lago_optimization(), replaced
center_sample_size for center level data, and weighted the
fit even when no weights were given. A row with a missing value in that
column was also dropped from the fit. The outcome model now always uses
the weights argument, or center_sample_size
for center level data, and a column named weights is used
only where it is named, such as an additional covariate. This also
applies to lago_sensitivity() and
lago_budget(). Results from data with a
weights column should be recomputed.
Continuous confidence covariance now uses the actual fitted model’s prior weights. Identity covariance uses weighted bread and residual scores, with estimated residual scale divided by the positive weight row count minus the design column count when no clusters are present. Logit covariance retains the existing Gauss Newton bread and HC0 or CR0 sandwich, now with weighted scores. Both clustering dimensions and their intersection use the same fitted weights. Weights are relative observation weights, so a common positive rescaling leaves covariance unchanged for a fixed fit. Zero weight rows contribute nothing, malformed fitted weights are refused, and unclustered identity covariance requires positive residual degrees of freedom. Binary covariance and existing finite unit weight results are unchanged. This extends the existing continuous formulas, not a frequency weight, survey design or general GLM covariance. The existing continuous quasibinomial family variance mismatch is not changed. Results from continuous fits with nonconstant weights should be recomputed.
Fixed recommendations and confidence output that depended on global contrast options or polynomial coding of ordered covariates. Every categorical predictor in the outcome model now uses treatment contrasts, including factor, ordered factor, character and logical covariates and center characteristics. This overrides explicit contrast attributes without changing input data or global options. Additional categorical covariates are held at their reference level. For a categorical center characteristic with two levels, optimization values 0 and 1 select the reference and other level. Numeric predictors and the existing center and period checks are unchanged. Results from affected runs should be recomputed.
Fixed wrong results when the center or
period column was not coded with one effect per level.
glm() fits an ordered factor with polynomial contrasts, and
a global options(contrasts = ...) such as
contr.sum changes the coding of every factor, but the
package read the resulting coefficients as one effect per center or
period. With fixed center effects, a binary outcome or any run without
the confidence set returned a wrong recommendation and estimate without
any error, and a continuous outcome failed when it computed the
confidence set. With fixed time effects an ordered period was refused
except at its first level, and under a global coding such as
contr.sum or contr.helmert whether a period
was refused depended on its label. A period that was not refused could
give a wrong result in the same way. The outcome model now always fits
center and period with treatment contrasts,
and an ordered center or period is converted
to an unordered factor with the same levels, with a message. Results
from such runs should be recomputed.
Fixed center or time effects with fewer than two centers (or
periods) are now refused with a message that says whether the data has
no observed period, only one center (or period), or only one with a row
the outcome model can use, and names that level, instead of failing
inside glm() with “contrasts can be applied only to factors
with 2 or more levels”. A fit with center or time effects where no row
is usable at all is refused with a message that says so.
Fixed results that used rows the outcome model never fitted.
glm() drops a row with a missing value in any model
variable, but the confidence set was computed over every row, and the
center weights covered every center in the data. With a continuous
outcome and missing values the confidence set came back empty or wrong,
with or without fixed center or time effects, and in some cases the call
failed (for example with fixed center effects and a missing covariate,
or a missing period with fixed time effects), so such results should be
recomputed. A center whose every row has a missing model variable still
got a weight: lago_optimization() failed when it computed
the confidence set, and otherwise (without the confidence set, after
shrinking, and in lago_sensitivity() and
lago_budget()) it returned results with mismatched center
weights, silently or with at most R’s generic “longer object length”
warning (none when the number of centers is a multiple of the number
fitted), so every run with such a center should be recomputed. The
confidence set now uses the rows the model was fitted on, and the center
weights now cover only the centers it fitted. A center the model could
not fit is left out of the default weights with a warning that names it
(the other centers are still sized by all of their rows, and the
shrinking target and default grid steps still use every row, since the
stage-1 intervention was delivered either way), and naming it in
center_effects_optimization_values, or giving it a non-zero
weight in center_weights_for_outcome_goal, is refused with
a message that names it. A factor center column with unused
levels no longer shifts the default weights onto the wrong centers, and
an explicit NA center level (as from addNA())
is kept as a center when another center is left out (a continuous
outcome’s confidence set still cannot use an explicit NA
level that a fitted row has in the center or period column, or as a
non-reference level of any other factor column, and now says so instead
of reporting a missing cluster id or returning an empty set), and a row
with a missing center no longer counts as a facility when checking
center_weights_for_outcome_goal. For center level data, a
center with no center_sample_size on any row is refused
with a message that names it when the default weights are computed, and
a center whose size is missing on only some rows is sized by its first
observed value.
Fixed lago_optimization() failing for a continuous
outcome when the data is a tibble, such as the bundled
BB_data. With the identity link it failed with “undefined
columns selected” when fixed center or time effects were included, and
with the logit link it failed with “Not compatible with requested type”
with or without them.
The in-browser playground now supports fixed center effects: tick
“Fixed center effects” to adjust the outcome model for each center,
matching lago_optimization()’s
include_center_effects, and choose whether to optimize for
the size-weighted average center or for one named center
(center_effects_optimization_values). The center column
comes from the data’s own center column when it has one,
used as is, otherwise from a column you pick (site_name for
BB_data), and the shown R code includes the line that
builds it on a copy of the data. Fixed center effects replace center
characteristics, which the package cannot fit together, and the center
column is not offered as a component or an additional covariate. The
sensitivity sweep, budget search and Share link carry the setting. A
center with no row where all the model columns are observed is left out
of the fit, and the page asks for another center if you chose it as the
one to optimize for.
The documentation of
center_effects_optimization_values now says it is a single
center name (a character string), which is what
lago_optimization() has always required, rather than a
numeric vector.
Fixed an outcome goal or a power goal failing when the outcome
column has missing values. The check of the outcome goal’s direction
compared it against the outcome mean without removing missing values, so
any missing outcome stopped lago_optimization() with
“missing value where TRUE/FALSE needed”. The power calculation counted a
row with a missing outcome toward its arm’s size but made the arm’s sum
missing, so the default unconditional approach failed the same way and
the conditional approach silently returned a power-implied outcome of 0,
which dropped the power goal. Both now use only rows with an observed
outcome, as the final treatment-versus-control test does, and a power
goal now needs a group column with both arms present and an
observed outcome in each.
Fixed the in-browser playground misreading a column named like a
built-in JavaScript property (for example constructor or
toString). Ticked as an intervention component, it made the
generated R code, Run, the sensitivity sweep and the budget search fail,
and its bounds and unit cost came up collapsed or blank instead of the
column’s defaults. As a center characteristic, its held-at value came up
blank and was left out of the call.
The in-browser playground now supports a power goal (binary
outcomes): tick “Power goal” to plan for the next stage’s statistical
power alongside, or instead of, the outcome goal, matching
lago_optimization()’s power_goal,
num_centers_in_next_stage,
patients_per_center_in_next_stage,
power_goal_approach, icc and
power_goal_cluster_id. The treatment arm comes from the
data’s own group column when it has one (used as is, so it
must hold only “treatment” and “control” with both present), otherwise
from a complete 0/1 column you pick (pre_post for
BB_data, as in the package tests), and the shown R code
includes the lines that build it on a copy of the data. Direction must
be Maximize, and Run stays disabled with a hint until the power settings
are complete. The ICC cluster column list offers only columns with at
least two non-missing centers with an observed outcome in each arm,
checked when an ICC is entered. The sensitivity sweep can vary the power
goal, and the budget search and Share link carry the settings. On
BB_data the stage-1 data already powers the comparison, so
the power goal alone recommends no intervention, and the printed result
reports the power-implied outcome.
The in-browser playground now has an optional “Additional
covariates” section: tick any columns (numeric or not, for example a
site or arm label) to adjust the outcome model for them, matching
lago_optimization()’s additional_covariates. A
column can play only one role at a time (intervention component, center
characteristic, or additional covariate). The chosen covariates ride
along in the Share link.
The in-browser playground now supports fixed time effects: when
the loaded data has a period column (so, an uploaded CSV,
since no bundled dataset has one) a “Fixed time effects” checkbox
appears with a period value to optimize at, matching
lago_optimization()’s include_time_effects and
time_effect_optimization_value. (Time effects are not
carried in the Share link, which is offered only for bundled
datasets.)
The in-browser playground now has an optional “Interaction terms”
section: with two or more intervention components ticked, tick a pair to
model their product (for example
coaching_updt × launch_duration). It sets
include_interaction_terms = TRUE and
main_components, appends the a:b term to
intervention_components, and prepends the line that creates
the interaction’s product column so the shown R code stays
copy-pasteable. Bounds, costs and the confidence set stay per main
component, and the sensitivity sweep, budget search and Share link
inherit the interactions.
The in-browser playground’s “Share link” now carries the whole page, not just the core optimization: the center characteristics and their held-at values, the interaction terms, the additional covariates, a budget you have entered, and the sensitivity sweep’s input, range and step count all round-trip through the URL, so an opened link reproduces the full setup. Older or hand-trimmed links that omit these simply keep the page’s own defaults for those controls.
The in-browser playground now has an optional “Center
characteristics” section: tick numeric columns (for example a center’s
birth volume) and set the value each is held at, and the recommendation
is computed for a center with those characteristics, matching
lago_optimization()’s center_characteristics
and center_characteristics_optimization_values. The
sensitivity sweep and budget search inherit the same setting. A column
can be an intervention component or a center characteristic but not
both.
The in-browser playground now has a “Budget” section that
surfaces lago_budget(): enter a cost budget and it finds
the best outcome reachable within it and the intervention that reaches
it (the reverse of setting an outcome goal). It reports the
recommendation, its cost and estimated outcome (or that no intervention
fits), notes when the budget does not bind, and draws the cost/outcome
frontier as an interactive D3 chart with the budget line and the chosen
point marked. The budget field auto-fills a sensible default from the
current costs and bounds until it is edited.
Added lago_budget(), which answers the reverse of
the usual LAGO question: given a fixed cost budget, what is the best
outcome reachable, and with which intervention? It searches the outcome
goal along the least-cost frontier lago_optimization()
traces out and returns the most ambitious reachable goal whose
recommended cost fits the budget (the lowest reachable outcome within
budget for outcome_goal_intention = "minimize"), with
print() and plot() methods and a
frontier of the cost-vs-outcome points searched. Like
lago_sensitivity(), it reuses a fitted lago
result’s call or takes the arguments directly, and it never computes the
confidence set. It reports whether the budget is feasible and whether it
binds.
Fixed the in-browser playground’s default configuration not
producing a result. It prefilled the first binary column as the outcome,
which for BB_data is the pre_post pre/post
period flag: perfectly separable from the coaching components, so the
outcome model never converged and Run (and the sensitivity sweep)
errored out of the box. Each bundled dataset now starts from a preset
that fits (BB_data uses the
pp3_oxytocin_mother outcome with the
coaching_updt and launch_duration components;
mtcars uses mpg with gear and
qsec), and switching datasets or pressing Reset lands on
that dataset’s runnable preset. Uploaded CSVs keep the generic
prefill.
The in-browser playground now has a “Sensitivity sweep” section
that re-runs the recommendation across a range of one input, using
lago_sensitivity(). Pick the input to vary (the outcome
goal or a cost multiplier), a from/to range and a number of steps, and
it draws how the recommended cost moves as an interactive D3 line (with
each run’s per-component recommendation, estimated outcome and status in
a table beneath). The confidence set is skipped during the sweep for
speed, and a failed run is shown as a gap rather than aborting the
sweep.
The in-browser playground now has a “Share link” button that encodes the whole current setup (bundled dataset, outcome column and type, intervention components, their bounds and costs, the goal and direction) into the page URL and copies it to the clipboard. Opening that link restores the configuration on load, ready to run. Costs are encoded as per-component coefficient vectors, so a curve shaped in the cost designer is preserved along with plain unit costs. Sharing is offered only for the bundled datasets, since an uploaded CSV cannot be carried in a URL.
The in-browser playground and the cost designer now round-trip. A
“Shape costs in the designer” button opens visualize_cost()
in the browser pre-loaded with the intervention components, bounds and
unit costs configured in the playground (the designer reads this from
its page URL query and falls back to its built-in example when the query
is absent or malformed; a plain visualize_cost(...) call is
unchanged). In the designer, a “Use these costs in the playground”
button sends the shaped cost functions back to the playground tab, which
then uses them as cost_list_of_vectors for the optimization
(shown in the reproducible R code) instead of the linear unit cost;
adjusting a component’s unit cost reverts it.
visualize_cost() now has a “Cost function form”
toggle that switches all components between the linear and cubic cost
functions live, instead of the form being fixed for the whole session by
default_cost_fxn_type. Switching resets each component to
that form’s initial coefficients and shows the right number of
coefficient sliders (2 for linear, 5 for the cubic’s degree-4 total
cost). The cubic form opens on a visibly curved (but still valid) demo
cost rather than the near-linear one cost_fxn_calculator()
returns for typical unit costs, so the toggle clearly shows a cubic.
This also gives the in-browser cost designer a cubic option.
The total cost curve in visualize_cost() can now be
reshaped by dragging: each curve carries a draggable handle at several
points along it, and dragging one refits the cost function through the
new set of points (and updates the sliders), instead of the single
right-endpoint handle that only rescaled the whole curve.
The fitted outcome model’s Call:, shown in the
“Outcome model fit” section of print() and
summary(), now displays the actual model formula instead of
the literal word formula. This also makes the console
output identical across R versions: a change in recent R to how the
unrecorded formula deparses had otherwise made the output (and its
snapshot tests) version dependent.
Added a live in-browser demo to the documentation site
(live-demo.html) that runs the real package client-side
with webR (R compiled to WebAssembly), so anyone can try
lago_optimization() with no installation, plus an
interactive playground (playground.html) where you pick a
bundled dataset or upload a CSV, configure the model with sliders and
toggles, and see the recommendation drawn with the package’s own D3
charts alongside a copy-pasteable R snippet. A GitHub Actions workflow
builds the package to WebAssembly with the rwasm toolchain and publishes
it as a small CRAN-like repository alongside the site.
Added a browser version of visualize_cost(): the
same Shiny cost-function designer now runs client-side on the
documentation site (/visualize-cost/), exported with
shinylive, so you can shape each component’s cost curve and copy the
coefficient list with no install. visualize_cost() was
refactored to build its app object separately from launching it, so the
local and in-browser versions share one implementation.
lago_report() now renders an interactive HTML
dashboard: the confidence set is a hover-enabled D3 plot (a scatter for
two components, a strip for one) with the recommended intervention
highlighted, and each intervention component gets interactive total-cost
and marginal-cost curves. The report stays a single self-contained
offline file (D3 is inlined, no CDN or server) and its API is unchanged;
rendering now also uses jsonlite (a new Suggests).
Added an MCP (Model Context Protocol) server to the Python
package (python -m lago.mcp_server) that exposes
optimize and sensitivity as tools any
MCP-aware AI agent can call, plus a sensitivity() function
in the Python wrapper.
Added lago_sensitivity(), which re-runs an
optimization across a sweep of one input (an outcome or power goal, or a
"cost_multiplier" that scales all costs) and reports how
the recommended intervention, its cost, and the estimated outcome move,
with print() and plot() methods.
The package is now installed and loaded as
LAGOtrials (call library(LAGOtrials)); the
previous LAGO identifier clashed with an archived CRAN
package. Function names, the lago result class, and the
LAGO method name are unchanged.
visualize_cost() now stores the returned cost list
in the lago_cost_list option instead of assigning it to the
global environment; retrieve it with
getOption("lago_cost_list").
Added tests for the interaction-terms optimization path and the
outcome-model fit warnings, and excluded the interactive
visualize_cost() app from the coverage figure so it
reflects the testable R code.
Added a CITATION.cff so the repository can be cited,
plus contributing guidelines, a code of conduct and issue/pull-request
templates. (#86)
Added test-coverage reporting, cross-platform (macOS, Windows, Linux) continuous integration, project-status and coverage badges, and a social-preview card. (#83, #84, #85)
Added a Python wrapper (in python/, importable as
lago) that calls LAGOtrials through rpy2.
(#81)
visualize_cost() now draws its cost curves
client-side with D3, with hover read-outs, a draggable curve endpoint,
and invalid-state highlighting. (#80)
The clustered variance estimator for logit outcomes is now
computed by a compiled Rcpp kernel, so Rcpp is a new
dependency. (#79)
lago_optimization() now refuses an additional
covariate whose column is entirely NA, naming it.
(#77)
A rank-deficient outcome model is now refused up front with an error naming the aliased terms. (#74, #76)
Added a warning when glm() drops an additional
covariate as collinear. (#76)
Fixed confidence-interval bounds for a binary logit outcome being
reported outside [0, 1]. (#75)
Fixed the estimated outcome being scaled by center weights that did not sum to 1. (#72, #75)
Added a warning when a binary outcome’s estimated outcome is
reported outside [0, 1]. (#75)
Added a warning when a numeric additional covariate observed away from 0 is held at 0 for the confidence set. (#75)
Fixed the confidence interval and set being computed on the logit scale for an identity-link binomial model. (#74)
center_weights_for_outcome_goal must now be numeric,
finite and non-negative, checked at both entry points. (#73,
#74)
Fixed the numerical optimizer failing with an opaque error when every restart failed, so it now gives an actionable message. (#73)
Fixed a factor covariate named like “center” or “period” being
miscounted as a fixed effect when get_confidence_set() is
called directly. (#73)
Fixed outcome_goal_intention = "minimize" ignoring
the outcome goal on a logit link. (#70)
Fixed the recommended intervention sometimes falling outside the intervention bounds. (#70)
Fixed the numerical optimisation returning its most expensive candidate solution instead of the cheapest. (#70)
Fixed the estimated outcome being wrong when more than one center characteristic was supplied. (#70)
link = "probit" and link = "log" are no
longer accepted, since only logit and identity
were ever implemented. (#70)
Fixed $est_outcome_ci sometimes reporting the wrong
interval instead of the one at the recommended intervention.
(#68)
Fixed the confidence set occasionally dropping one of its qualifying interventions. (#68)
Fixed confidence_set_size_percentage understating
the size of the confidence set. (#68)
A confidence set containing exactly one grid intervention is no longer discarded as empty. (#68)
Fixed confidence-interval bounds being attached to the wrong interventions under two-way clustering. (#68)
get_confidence_set() now returns
rec_int_ci, the interval at the recommended intervention
(its return shape changed for direct callers). (#68)
The confidence set, its size, and the estimated-outcome interval can differ from earlier releases wherever the previous values were wrong. (#68)
Fixed get_confidence_set() pairing the prediction
matrix with the model coefficients by position instead of by name.
(#68)
Fixed the estimated outcome being reported as 0 for the reference period when fixed time effects were included. (#68)
Fixed the estimated-outcome interval being computed at the wrong time period. (#68)
Fixed an additional covariate or center characteristic named like “center” being miscounted as a fixed center effect. (#68)
Fixed a factor or character additional covariate or center characteristic being paired with the wrong coefficient or erroring. (#68)
Fixed the confidence set changing when the rows of
data were reordered. (#68)
Fixed passing more than one center characteristic to the confidence set failing or duplicating a column. (#68)
Added runnable @examples to every exported function
that lacked them. (#67)
Restyled the print() and summary()
console output with boxed cli sections. (#65)
The fitted outcome model ($model) and the
estimated-outcome interval ($est_outcome_ci) are now
returned on the result. (#65)
Added lago_report(), which renders a self-contained
HTML report of a result. (#64)
lago_optimization() now returns a
"lago" object with print(),
summary(), and plot() methods. (#61)
Added an “Optimizing an intervention with LAGO” vignette. (#61)
Added a hex logo and refreshed the README header. (#56, #61)
Added a quiet argument to
lago_optimization() that skips progress messages.
(#60)
visualize_cost() gained a copy-to-clipboard button,
a numeric cost summary, and returns the cost list on close.
(#59)
Expanded the reference documentation for the bundled datasets. (#58)
Added a GPL-3 LICENSE file. (#56)
Fixed a crash when a single intervention component was used. (#54)
Added a pkgdown documentation site published to GitHub Pages. (#53)
The overall intervention-effect test result is now returned in the result object. (#49)
Added an automated test suite (testthat) and continuous-integration checks. (#45)
lago_optimization() now accepts a standalone power
goal, an outcome goal, or both together. (#40)
Added non-fatal outcome-model fit diagnostics. (#36)
visualize_cost() sliders now support per-component
default and custom ranges. (#34)
Supplying optimization_grid_search_step_size now
switches the optimization method to grid search automatically.
(#32)
Added an icc argument so the power goal can account
for within-center clustering. (#29)
outcome_goal_intention. Under
outcome_goal_intention = "minimize" it can therefore
contain interventions whose estimated outcome is above the goal, and
which cost more than the recommendation, because their interval still
reaches the goal from above. Read the confidence set as the
interventions the data cannot distinguish from the goal, not as the
interventions that meet it. The estimated outcome and its interval,
reported as est_outcome_goal and
est_outcome_ci, are computed at the recommended
intervention and are direction independent, so they are unaffected.