LAGOtrials (development version)
The help pages now render their formatting. Markdown was never turned on for the package, so a link such as [lago_optimization()] and the code marks around an argument name were printed as typed. Links now work, code is set in code font, and the lists of cost functions and of optimization methods are real lists. The text itself is unchanged, apart from three places where a sentence that followed a list ran into its last item.
lago_app()now opens the cost designer ofvisualize_cost()in a dialog, from the “Shape the costs visually” button in the intervention step. The designer starts on the costs in use, draws the total and marginal cost of each component, and lets you shape them with sliders or by dragging the curve. “Use these costs” puts them in as cost coefficients and keeps the bounds. Costs that do not fit the designer’s linear or cubic form are named in the dialog, since applying replaces them.lago_app()has two new tabs that start from the result of a run. Sensitivity runslago_sensitivity()over even steps or typed values of the outcome goal, a multiplier of all costs, the power goal settings or the period, and shows a plot, a table and a CSV download. Budget runslago_budget()for a budget that starts at the cost of the recommendation, explains whether the budget binds, plots the goals searched, and can put the best outcome in as the outcome goal. The Results tab can download the HTML report oflago_report(). The R code tab and the value returned by Done include the sweep and the budget search.Fixed the sliders of
visualize_cost()moving a coefficient when they only got the keyboard focus. A slider now keeps its value when you tab through it, and the arrow keys move it by one step.
LAGOtrials 1.2.0
CRAN release: 2026-11-11
New
lago_app()runs the whole LAGO optimization in a point and click Shiny app on your own computer, with no R code to write (#37). It covers the data (your own data frame, the bundled examples or a CSV file), the outcome and goal with the power goal, the intervention components with their bounds and costs, interaction terms, center characteristics, fixed center and time effects, and additional covariates. Each step says what is missing before a run, every setting has a short explanation, and the result shows the recommendation, the confidence set and the package’s warnings. The app shows the R code that reproduces the run, and returns the last result to R when you click Done. It is reachable only from your own computer.visualize_cost()now also serves its app only to your own computer, even when theshiny.hostoption is set to another address.Fixed the numerical optimizer trusting a solver result outside the intervention bounds.
NlcOptim::solnl()can return such a point, far outside the bounds with some BLAS libraries, as on Ubuntu 26.04 with OpenBLAS 0.3.32. The highest outcome search then took an outcome only reachable outside the bounds, so an unreachable goal looked reachable and the recommendation was the upper bounds instead of the shrinking method. The cost search let such a point set the lowest cost, so cheaper points inside the bounds were left out and a costlier recommendation was returned. Every solver result is now moved onto the bounds before its outcome, its cost and the goal are checked, so results no longer depend on the platform.
LAGOtrials 1.1.1
CRAN release: 2026-11-04
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 toprev_recommended_interventionswhen 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()andlago_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 thatglm()could not estimate is no longer reported as dropped.get_confidence_set()now refuses, with a clear message, a model with any other coefficientglm()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 nowNULL, which means TRUE for center level data and FALSE otherwise. Center level data also refuses anoutcome_nameother than"proportion", the column it is fitted on, instead of fittingproportionwhile 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 theglm()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_sizemust 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_typeorunit_cost_dosesinlago_sensitivity()orlago_budget()on a stored result now replaces the storedcost_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
optimizeandsensitivitytools now take the next-stage size,power_goal_approach,iccandpower_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
iccwheniccis 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 unlesslinkwas 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 bothcenter_weights_for_outcome_goalandcenter_effects_optimization_valuesare given, since the center weights are used and the named center is ignored. It also warns thatweightsis ignored for center level data, whose rows are weighted bycenter_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,affordableand the others), which used to change its result. The printed summary oflago_sensitivity()now pairs each cost with its own value, so a descending sweep is no longer described backwards, and itsbaselineattribute is found for a value built withseq().When the outcome goal cannot be reached,
print(),summary(),plot()andlago_report()now say that the shrinking method was used and no confidence set is computed, rather than telling the user to setinclude_confidence_set = TRUE. The result has a new fieldshrinking_method_used.print()of alago_budget()result now says minimum for a minimize budget that does not bind.get_confidence_set()now accepts a single clustering vector ascluster_idand 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_dosesinlago_optimization(): the dose each unit cost inunit_costswas 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
costcolumn 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_sizepatients, 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()andlago_report()now shows each estimate with the standard error the intervals use, and says which one it is. It used to show theglm()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()andlago_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 itsloweranduppernames, which the printed output and the Python wrapper read.print(),summary(),plot()andlago_report()now label the interval and the confidence set with the level they were computed at, fromconfidence_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. Withn_grid = 2its refinement pass re-ran the same two goals, so nothing was refined, and it now tries goals strictly inside the gap. Other results oflago_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 itscenter_sample_size, which is howglm()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, soexpit()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_sizepatients and its events, as for the same data at the patient level, including the stage-1 cluster sizes used withicc. A row withoutcenter_sample_sizeis left out, asglm()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 inintervention_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_thresholdnow 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
NaNperiod as a missing value when it checks which centers have a row the outcome model can use. The package turns a numericperiodcolumn into a factor, which keepsNaNas 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. Whenperiodis used as a numeric intervention component or covariate instead, aNaNstill counts as missing, as it does inglm().Three input problems now get clearer refusals.
center_effects_optimization_values = NA_character_with acentercolumn that has missing values but no explicitNAlevel failed with “values in center_weights_for_outcome_goal must sum up to 1”, and now says thatNA_character_is not one of the centers and how to make the missing centers an explicit level withaddNA(). The three refusals ofcenter_effects_optimization_valuesno 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 globaloptions(na.action = "na.pass")or"na.fail"with a missing value in a model variable or its glm weight failed insideglm()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 explicitNAlevel (as fromaddNA()) both became, for example,centerNA, and a factorvisitwith level2and a numeric columnvisit2both becamevisit2. The package reads coefficients by name. With fixed center effects andinclude_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 ofNAlevel 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 returnsfeasible = FALSE, but its warning and printed result now give the error of the first failed run, and the newrun_errorfield holds it. The printed result no longer suggests a larger budget in that case.The documentation of
get_confidence_set()now says thatcenter_weights_for_outcome_goalhas one weight per center of the fitted model, in the order of its center levels, and the documentation ofcenter_effects_optimization_valuesnow says thatNA_character_selects an explicitNAcenter level. The README examples now show the outcome model’s actual printed call.lago_sensitivity()andlago_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 onBB_datawent 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 singlelago_optimization()call always fits its own model.lago_sensitivity()andlago_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 thecentercolumn was converted to a factor and the note thatoptimization_methodwas 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 withquiet = FALSEstill appear for every run. The note thatdefault_cost_fxn_typeis ignored whencost_list_of_vectorsis given is now sent as a message like the other input notes, rather than printed withprint(), so it no longer shows as[1] "..."in the output.
LAGOtrials 1.1.0
CRAN release: 2026-10-24
Fixed two unclear refusals for fixed center and time effects. An all
NAcentercolumn was refused with “values in center_weights_for_outcome_goal must sum up to 1”, or with a message about the length ofcenter_weights_for_outcome_goalwhen the weights were given. It is now refused with the same kind of message an allNAperiodcolumn 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 withcontr.sum,contr.SAS, a globaloptions(contrasts = ...)that sets another coding, the default coding of an ordered factor, or a treatment matrix such ascontr.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 bylago_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
weightsreplacing the weights of the outcome model.glm()looked up itsweightsargument in the data first, so such a column replaced theweightsargument oflago_optimization(), replacedcenter_sample_sizefor 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 theweightsargument, orcenter_sample_sizefor center level data, and a column namedweightsis used only where it is named, such as an additional covariate. This also applies tolago_sensitivity()andlago_budget(). Results from data with aweightscolumn 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
centerorperiodcolumn was not coded with one effect per level.glm()fits an ordered factor with polynomial contrasts, and a globaloptions(contrasts = ...)such ascontr.sumchanges 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 ascontr.sumorcontr.helmertwhether 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 fitscenterandperiodwith treatment contrasts, and an orderedcenterorperiodis 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 inlago_sensitivity()andlago_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 incenter_effects_optimization_values, or giving it a non-zero weight incenter_weights_for_outcome_goal, is refused with a message that names it. A factorcentercolumn with unused levels no longer shifts the default weights onto the wrong centers, and an explicitNAcenter level (as fromaddNA()) is kept as a center when another center is left out (a continuous outcome’s confidence set still cannot use an explicitNAlevel 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 checkingcenter_weights_for_outcome_goal. For center level data, a center with nocenter_sample_sizeon 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 bundledBB_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()’sinclude_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 owncentercolumn when it has one, used as is, otherwise from a column you pick (site_nameforBB_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_valuesnow says it is a single center name (a character string), which is whatlago_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 agroupcolumn 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
constructorortoString). 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()’spower_goal,num_centers_in_next_stage,patients_per_center_in_next_stage,power_goal_approach,iccandpower_goal_cluster_id. The treatment arm comes from the data’s owngroupcolumn 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_postforBB_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. OnBB_datathe 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()’sadditional_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
periodcolumn (so, an uploaded CSV, since no bundled dataset has one) a “Fixed time effects” checkbox appears with a period value to optimize at, matchinglago_optimization()’sinclude_time_effectsandtime_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 setsinclude_interaction_terms = TRUEandmain_components, appends thea:bterm tointervention_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()’scenter_characteristicsandcenter_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 frontierlago_optimization()traces out and returns the most ambitious reachable goal whose recommended cost fits the budget (the lowest reachable outcome within budget foroutcome_goal_intention = "minimize"), withprint()andplot()methods and afrontierof the cost-vs-outcome points searched. Likelago_sensitivity(), it reuses a fittedlagoresult’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_datais thepre_postpre/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_datauses thepp3_oxytocin_motheroutcome with thecoaching_updtandlaunch_durationcomponents;mtcarsusesmpgwithgearandqsec), 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 plainvisualize_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 ascost_list_of_vectorsfor 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 bydefault_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 onecost_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 ofprint()andsummary(), now displays the actual model formula instead of the literal wordformula. 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 trylago_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 usesjsonlite(a new Suggests).Added an MCP (Model Context Protocol) server to the Python package (
python -m lago.mcp_server) that exposesoptimizeandsensitivityas tools any MCP-aware AI agent can call, plus asensitivity()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, withprint()andplot()methods.The package is now installed and loaded as
LAGOtrials(calllibrary(LAGOtrials)); the previousLAGOidentifier clashed with an archived CRAN package. Function names, thelagoresult class, and the LAGO method name are unchanged.visualize_cost()now stores the returned cost list in thelago_cost_listoption instead of assigning it to the global environment; retrieve it withgetOption("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.cffso 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 aslago) that calls LAGOtrials throughrpy2. (#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
Rcppis a new dependency. (#79)lago_optimization()now refuses an additional covariate whose column is entirelyNA, 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_goalmust 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"andlink = "log"are no longer accepted, since onlylogitandidentitywere ever implemented. (#70)Fixed
$est_outcome_cisometimes 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_percentageunderstating 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 returnsrec_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
datawere reordered. (#68)Fixed passing more than one center characteristic to the confidence set failing or duplicating a column. (#68)
Added runnable
@examplesto every exported function that lacked them. (#67)Restyled the
print()andsummary()console output with boxedclisections. (#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 withprint(),summary(), andplot()methods. (#61)Added an “Optimizing an intervention with LAGO” vignette. (#61)
Added a hex logo and refreshed the README header. (#56, #61)
Added a
quietargument tolago_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
LICENSEfile. (#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_sizenow switches the optimization method to grid search automatically. (#32)Added an
iccargument so the power goal can account for within-center clustering. (#29)
Known limitations
- The confidence set is the set of interventions whose confidence interval covers the outcome goal, which is a two sided test and does not depend on
outcome_goal_intention. Underoutcome_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 asest_outcome_goalandest_outcome_ci, are computed at the recommended intervention and are direction independent, so they are unaffected.