You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
A form to share a case study of your own. The issue form case_study.yml asks for the least an analysis
needs: the factors and responses, the runs in the order they were done, how the runs were grouped (in a random
order, in blocks, or as a split plot), and the licence the data are shared under (CC BY 4.0 or CC0 1.0). The
numbers the contributor's own analysis found are optional; each one given becomes a check. The design catalog
links to the form beside the one for proposing a published design, and an entry made from it cites the issue
as its source.
The reference catalogue holds Anderson and Whitcomb (2007), DOE Simplified.anderson-whitcomb-2007
holds the book's 30 experiments (16 worked examples and 14 practice problems) and 16 design layouts, with 668
printed numbers: simple comparisons and blocking, two-level factorials and fractions with their aliases, an
irregular 3/4 fraction, minimum-run resolution IV and V designs, fold-overs, general factorials, centre points
augmented to a central composite design, desirability, two mixture designs, and a table of power for five
factors. The library reproduces 578 of the 668. Another 23 differ for reasons the book's own data show: a
p-value copied from the row above, two effects swapped between rows, F ratios worked from a rounded mean square,
and the two fits behind the confetti example, Table 8-5 at axial distance √2 and the text at the 1.4 it prints.
The other 67 have no check, and their reasons name what the library lacks: the irregular and
minimum-run designs, an alias matrix against three-factor interactions, and plotting positions for raw data.
Factorial: Learn's "What if" panel predicts every response at the settings the reader chooses (#899). A
slider and a number box set each numeric factor inside its range, and a list sets each categorical factor's
level. Each response's prediction, its 95% confidence interval for the mean, its 95% prediction interval for one
new run and its leverage follow each move at once: Python sends each response's fitted model as numbers once per
analysis, and the page predicts from them in its own JavaScript. A flag says when the settings lie beyond the runs
a model was fitted to, and a sentence names each limit on the settings they break. The panel opens at the balanced
settings, else the best settings, and comes back to them with one button; its Python panel prints the same
predictions with analyze_experiment.
Factorial: runs given as a regular two-level fraction say its resolution and defining relation (#923).
Runs a reader hands over, or the catalog opens as its source prints them, are read for the fraction they make:
"Resolution III: the runs make the defining relation I = A·D·E = B·C·D = A·B·C·E", as a fraction the page makes
states its own. Runs that make no such fraction, as a Plackett-Burman design, say nothing of it.
analyze_experiment(transform="log10") fits the common log of the response. Its F tests and p-values are
those of transform="log"; its sums of squares, coefficients and predictions are on the log10 scale, as
Design-Expert prints them, so Table 4-4 of Anderson and Whitcomb's DOE Simplified reproduces as printed.
The analyze_experiment tool accepts it too.
A prediction from a transformed response is also given in the response's own units. With analyze_experiment(transform=...), each record of predictions holds response_units: the prediction put
back (the response's median, which a monotone transform keeps, not its mean) and the ends of both intervals
put back, low before high. An end the transform leaves unbounded, such as a 1/y interval through 0, is None.
signal_to_noise(readings, kind) works Taguchi's signal-to-noise ratios. Larger is better, smaller is
better and nominal is best, in decibels, of one setting's readings or of a table of them with a row for each
setting; a blank reading is left out. It is exported from process_improve.experiments, beside cross_arrays, and the reference catalogue's sn_* statistics use it.
The browser app analyses a response as measured, or as its log, square root or inverse. A choice beside
the model fits the response on that scale (the inverse as -1/y, which keeps the results' order and tests as 1/y
does), and the study, its saved link and its workbook keep it. When Box-Cox's interval for lambda leaves out 1
and holds one of the three, the analysis says so and offers it in one click. On the log scale each main effect
is also shown as a ratio, from the factor's low to its high. Everything said in the response's units is put
back in them: the response surface, the best settings and their intervals (the prediction is the median), the
what-if panel, and the balance of several responses, whose desirability is worked on each model's scale
between the reader's own ends. A level comparison stays on the results as measured, the next step says its
numbers are on the model's scale, and the "Show the Python" code fits the same transform. A mixture's
responses are analysed as measured.
The browser app reads the spread of the results repeated at each setting. When two settings or more are
run twice or more, as a replicated Taguchi array is, Learn sums up each setting's readings: their mean,
standard deviation, ln s and the signal-to-noise ratio of the response's goal (larger is better to maximise,
smaller is better to minimise, nominal is best for a target). The response table averages each at every level
of each factor, with its delta and rank, and says at which level the spread is smallest and the ratio highest.
For a target it names the adjustment factor: the one that moves the mean most for its effect on the spread.
"Show the Python" gives the same table with pandas.
budget sizes a fractional factorial (#956).generate_design(factors, design_type="fractional_factorial", budget=32) builds the minimum-aberration fraction with that many
runs, so sizes between two resolutions, such as 2^(7-2) and 2^(8-3) in 32 runs, no longer need
hand-written generators. The budget counts centre runs and replicates. A budget that leaves a number
of runs that is not a power of two warns (UserWarning) and rounds down to the largest power of two
that fits; a budget beyond the half fraction warns and gives the half fraction. With resolution as
well, the fraction must reach it, or a ValueError says so; before, a budget with a resolution gave
the fewest runs that reached the resolution, and it now gives the budget's size (budget=32, resolution=4 for 7 factors: 32 runs, not 16). With generators, or when the design type is chosen
automatically, the budget is a ceiling, as before.
Choose the number of components on the multivariate page. A "Cross-validate" button,
never run on its own, scores every number of components on values the model was not fitted
to and draws R² beside Q² with one standard error, the recommendation marked; clicking a bar
uses that number. In Python, process_improve.workbench.Variant.cross_validate does the same
over a recipe's table, leaving the excluded rows out, and returns a CrossValidation that
writes the code repeating it. For PLS, the targets weigh alike, as the fitted model weighs
them.
A multivariate page in the browser app, at mva/. PCA and PLS on the LDPE example or on your own CSV or
Excel file, with Python running in the page (Pyodide): a canvas data table whose column headers show each
variable's distribution, run order and missing values, with the selected rows drawn over them; linked score,
loading, SPE and T2 plots, where a lasso in one selects the rows everywhere; rows excluded from the model but
still drawn, hollow, where the refitted model projects them; "Why are these rows different?", which ranks the
variables behind a group's shift with their raw data beside the bars; recipe steps that write the Python that
repeats them; and project files that refit and check themselves when opened. The page shares the app's wheel
with the designed-experiments page.
process_improve.workbench: a recipe that refines a table step by step and writes the Python that repeats
it. A Recipe is a source file (SourceFile, with its SHA-256 digest) and a list of steps: drop, keep or
rename columns, drop rows with a reason, keep the rows where a condition holds, add a computed column,
transform a column (log, log10, sqrt, reciprocal, logit, signed log, signed power), read text as numbers, and
identify the rows by a column. Recipe.apply runs it, Recipe.to_python writes a pandas script that rebuilds
the same table bit for bit (and refuses a different file of the same name), and Recipe.to_dict / Recipe.from_dict round-trip it through JSON. Computed columns and conditions use a small expression language
that is parsed and never evaluated as Python, so a recipe read from someone else's file stays data.
process_improve.workbench.Variant: a PCA or PLS model over a recipe's table that excludes rows without
losing them. An excluded row is left out of the fit and of its centring and scaling, then projected through
the fitted model, so Fit.rows() still reports its scores, Hotelling's T2 and SPE, flagged. Variant.to_python
writes the model code that follows the recipe's script. Fit.explain_group answers what a cluster on a score
plot asks: per component, each variable's share of the difference between the group's average score and the
rest's (they add up to that difference), the variables to look at first, their means in the table's own units,
and their raw values in run order to draw beside the bars.
process_improve.workbench.Project: one zip file with a source file, its recipe, its model variants and the
reader's saved selections. It stores the source file's own bytes, a manifest.json, the recipe as a script
to run next to the source, and each variant's fitted scores, loadings, T2 and SPE as .npy arrays. Opening
executes nothing from the file (JSON checked field by field, and numpy.load(allow_pickle=False)), and Project.open refits every variant and compares it with the stored arrays, naming any that no longer
reproduce.
The reference catalogue holds Box and Draper (2007), Response Surfaces, Mixtures, and Ridge Analyses
(2nd edition), the whole book.box-draper-2007 holds 177 experiments and 145 designs from chapters 1 to 20
and the answers to the exercises, with 12,499 printed numbers: least squares and its matrix algebra, two-level
factorials and fractions, steepest ascent, second-order fits and their canonical analysis, transformations of the
response, ridge analysis, composite, Box-Behnken and other response surface designs, variance and bias of designs,
and mixture experiments. 8,746 checks agree with the library. 1,049 do not, and each says why: an error in the
book, the book's own rounding, a convention the library does not follow, or a library bug. 3,192 numbers are not
recomputed, and each says why: what the library lacks, such as ridge analysis under linear restrictions,
iteratively reweighted least squares, the bias of a design and mixture lattices of degree 4 and more, or the
library function that gives the number where no check reads it yet.
New check options in the reference catalogue:exp10 reads a fit to common logs back as an antilog; sign_of and abs on library_value give an eigenvector the sign a source prints; ss_total_uncorrected, ss_mean, ss_regression_uncorrected, df_model_uncorrected and ms_regression_uncorrected check an analysis
of variance about zero; the angular_degrees summary is arcsin (m/n)^1/2 in degrees, with a source's own values
at m = 0 and m = n.
Multivariate session tools for agents.mva_open, mva_fit, mva_explain, mva_predict and mva_script let an agent work a multivariate analysis step by step, as the Multi- and Megavariate book does: fit,
find the rows beyond the T² or SPE limits and the variables that set them apart, leave rows out or transform a
column, and fit again. The tools keep no state: each takes the analysis so far as a JSON session (the source,
its recipe, and every model with the one it grew from and why) and returns it with the step added, so the session
works in safe mode and is the whole record. mva_script writes the Python that rebuilds each model.
Reproduced from the books: a page of the design catalog at https://factori.al/catalog/reproduced/. It shows,
for every case of every source in the reference catalogue, how closely process-improve reproduces the numbers
the source prints, in four tiers: fully reproduced (every recomputed number agrees to the source's last printed
digit), within 1%, within 5%, and unable to reproduce. A table counts the cases and the numbers in each tier. A
table by source groups the sources by their form (books, journal papers, and handbooks, encyclopedias and
courses), with each group's totals and the share of each source's printed numbers that agree. A chart shows how
far off the differing numbers are. Each tier opens as a list of its cases, grouped by source, and each case lists
the numbers that differ with the printed value, the library's value, the relative difference, where the source
prints it and the reason the catalogue records. The page can be searched (a title, a quantity, a word of the
reasons such as "misprint"), filtered by source, form of source and kind of case, and its address keeps the
view, down to one case. The causes of the differences, and of the printed numbers not recomputed, are counted
for all the sources together, and a list of what the library lacks orders the missing methods by how many
printed numbers each would recompute.
Every difference and every number not recomputed in the reference catalogue now records its cause. A
check with a disagreement names one of reference_catalogue.DISAGREEMENT_CAUSES (an error in the source, a
quiz option its key marks incorrect, the source's rounding, another method, the library, or not known yet) and a
check with a manual reason one of UNCHECKED_CAUSES (not in the library yet, not read by a check yet, data not
printed, nothing to recompute); reproduce_reference_catalogue reports it in a cause column. A number the source
prints in error, or a quiz option keyed incorrect, does not count against a case's tier
(design_catalog.LIBRARY_RIGHT).
design_catalog.catalog_reproduction returns that classification for every case, or for one source, and REPRODUCTION_TIERS holds each tier's largest relative difference. design_catalog.source_form reads the
form of a source from what the catalogue records of it (SOURCE_FORMS). The browser app's build writes both to catalog/reproduction.json.
Changed
Factorial's design catalog counts its designs only (#923). An entry of facts about designs, or of one sample
with nothing varied, is listed but counted apart from the designs, after them: "and 3 entries of facts about
designs or a single sample".
A study of one factor that its source compares as levels opens with that factor categorical (#923).
Montgomery's plasma etching compares four RF powers, C(power): Factorial compares them with Tukey's intervals,
as the book does, and Learn shows 79 of the numbers the book prints for it, where it showed none.
The dev extra is removed (#513).pip install 'process-improve[dev]' installed the
contributors' test, lint and documentation tools, Sphinx among them, from the published wheel, and
its list had drifted from the dev dependency group that CI installs. Contributors install the
group with uv sync --dev --all-extras, as CONTRIBUTING.md now says. No runtime extra changes.
PCA.select_n_components with the element-wise k-fold scheme (cv_scheme="ekf", the default) runs about four
times faster on wide data. Each EM step now projects onto the leading components from the smaller Gram matrix
instead of computing a full SVD. The PRESS values are unchanged to rounding (1e-13 relative): 7-fold cross-validation
of the tablet spectra up to 20 components takes 103 s, where it took 445 s.
find_elbow_point, and f_elbow with it, scale both axes to span 0 to 1 before looking for the
elbow (#513). The elbow was the point nearest the lines' intersection by a distance that added
x units to y units, so it moved when the units did: on one test curve it was at index 24 with time
in hours, 26 in seconds, and 38 with y multiplied by 1000. It is now 24 for all three. The elbow
you get can move by a few points wherever the two axes span very different ranges.
panel_scorecard measures each panelist's agreement against the other panelists only (#513).
The consensus was the mean of every panelist, the one being scored included, and a mean that holds
a panelist's own scores correlates with them by construction. This changes the numbers you get: agreement falls, by most on a small panel. With two panelists both now get
the correlation between them (0.665 in the new test, where they got 0.928 and 0.896), and a panel
of one gets NaN instead of 1.0. Because the flags compare agreement with fixed levels (0.5, and
0 for anti-correlation), a panelist can now be flagged, and dropped by drop_panelists="auto" in analyze_descriptive, where it was not before. The same applies to the sensory_panel_check and sensory_analyze_descriptive tools.
mixed_assessor_model gives NaN F-tests, with a RuntimeWarning, for an attribute where a
panelist did not score every product (#513). Its sums of squares add up only on a complete
panelist-by-product table. With a missing cell the four F-test columns were sometimes NaN and
sometimes a finite number on the wrong degrees of freedom, with no warning; a finite value you got
before for such an attribute is now NaN. The warning names the attributes and points to mixed_assessor_model_reml, which handles missing cells. The scaling coefficients are unchanged.
PLS.select_n_components runs about twice as fast on wide data. The per-row and per-column sums of squares
that PLS and PCA fits keep at every component were computed one row or column at a time in Python; they are now one
vectorised call. Fitted values are unchanged (under 1e-12 relative), and cross-validated PRESS to 1e-9: 7-fold
cross-validation of the 650-column tablet spectra, 10 repeats to 20 components, takes 17 s, where it took 31 s.
The package is classified as production-ready on PyPI. Its trove classifier moves from Development Status :: 4 - Beta to Development Status :: 5 - Production/Stable.
A budget too small for the design raises a ValueError (#957). Optimal, mixture and
mixture-process designs used to raise such a budget and log a warning, so a caller who set the run
count as a limit got a larger design than asked for, with no Python warning to catch. They now refuse
it, and say how many runs are needed, when the budget is below the model's number of coefficients,
when it leaves too few runs for the block effects of n_blocks, or when it leaves too few new runs
for the coefficients that fixed_runs do not estimate. A budget left at its default is still sized
to the design, and now counts the block effects as well. metadata["budget_requested"], which
recorded the raised budget, is no longer set by these designs. The browser app refuses such a run
count in the same way, where it used to build the larger design and add a note saying so. augment_design still adds the runs an augmentation needs, and says so in its explanation.
augment_design(..., "add_runs_optimal") raises a ValueError when n_additional_runs is too few
(#957). When the existing runs left more model coefficients unestimated than the runs asked for, the
augmentation added more runs than requested and said so in its explanation. It now refuses, naming
how many new runs the target model needs, as generate_design refuses a budget that is too small.
optimize_responses(method="canonical_analysis") returns the ridge form as ridge_form, in place of b_canonical, with the same contents. The old key named Box and Draper's B canonical form, but it held their
ridge form of Eq. 11.1.2, y = y0 + theta X_k + sum of lambda_i X_i^2. The form centred on the stationary point, canonical_axes, is their B form; the docstring called it the A form, and now names both as Box and Draper
(2007) do. b_canonical is gone, with no alias.
Five arguments and result keys are named for what they are, not by a letter (no aliases, except as noted):
bibd_analysis returns n_treatments, n_blocks, block_size, replicates, concurrences and n_runs in place of a, b, k, r, lambda and n;
spe_plot and t2_plot (and the PCA and PLS methods) take n_components in place of with_a;
evop_worksheet takes range_factor_decimals in place of f_decimals;
variance_components reports each fixed effect's t_value and p_value, and each fixed term's test f_value and p_value, in place of t, p and f;
ControlChart.calculate_limits takes a known standard deviation as sigma in place of s; the fitted
attribute is still s. This one keeps s= as a deprecated alias.
A refused name now comes with a name that works. A factor, response or column name that a model formula
cannot hold is refused with a suggestion close to it: 'Water %' gives Water_pct, 'Temp (°C)' gives Temp_deg_C, '2nd stage' gives x2nd_stage and 'lambda' gives lambda_. analyze_experiment, evaluate_design, augment_design and ASCA refuse every unfit factor name in one error, with a map to paste
into DataFrame.rename(columns=...); a suggestion that another column already has gets _2, _3, and so on.
On factori.al, the factor table offers the suggestion as you type the name.
Deprecated
ControlChart.calculate_limits(s=...) is deprecated since 2.2.0 and will be removed in 3.0; use sigma=.
The keyword s still works and issues a DeprecationWarning. Passing both s and sigma raises ValueError.
Fixed
The boxplot tool leaves missing values out of each box (#513). One missing value made every quartile
of its box NaN, so the box came back as five nulls and drew as nothing, with no error. The quartiles,
whiskers and outliers are now computed from the values present. A column or group with no values at all
is refused by name, as {"error": "'y' has no values to plot: every value is missing."}.
ChartSpec.to_dict() is JSON-safe, as documented (#513). It converted enums only, so a layer's data
built from a DataFrame kept its numpy scalars and json.dumps raised on an np.int64. It now converts
them as every tool's output is converted (tool_spec.clean): numpy values become Python values, and
NaN and infinity become None.
The batch feature functions no longer add columns to the caller's DataFrame (#513). Without a batch_col or phase_col, every f_* feature inserted __batch_grouper__ and __phase_grouper__
into the frame it was given; they now go on a copy.
f_iqr omits missing values, as f_std and f_robust_mad do (#513). One missing value made
the whole batch's IQR NaN.
f_area works on a datetime or timedelta time axis, and measures it in seconds (#513). The
widths were timedeltas, so the area could not be stored as a number. A numeric axis is unchanged.
f_crossing gives NaN for a batch that never crosses the threshold, as documented (#513). It
gave an empty array.
batch_dtw keeps each batch's own key (#513). With keys of mixed types, such as 1 and "two", every key was cast to the type of the last one, so batch 1 came back as "1".
batch_dtw always runs its first iteration (#513). A tolerance at or above the square root of
the number of aligned columns skipped the alignment and raised an IndexError.
resample_to_reference refuses a batch of fewer than 2 rows, naming it (#513). It divided by
zero and then failed inside scipy with "x array is all-nan".
OLS gives a near-perfect fit its pi_range_ grid (#513). Below a residual standard error of
about 1e-7 the grid was NaN, and multiple_linear_regression left out its pi_range key; the band
now has the near-zero width that prediction_interval() already gave.
PCA.select_n_components(return_consensus=True) scales the block for parallel analysis
whatever scale_inside_folds is (#513). The flag was passed on as parallel_analysis(scale=...), so with scale_inside_folds=False a raw block was compared in its
own units against a unit-variance null, and parallel analysis retained every component of loud
noise. parallel_analysis_n_components and consensus can change for calls that passed scale_inside_folds=False on a block that was not already autoscaled.
MBPLS and MBPCA scale each block's Hotelling's T² as they scale the super T² (#513). Each
component is divided by the block scores' second moment about zero over the rows that have a
score, the convention explained_variance_ uses for the super scores. The block T² used the
variance about the scores' own mean, which differs once missing cells leave them off-centre, and
on an MBPLS fit with a row that has nothing observed in a block, that variance was NaN, so the
block's T² went unscaled for every row. block_hotellings_t2_ changes for fits with missing data,
slightly for scattered missing cells and by a large factor in the second case; on complete data it
is unchanged to rounding.
TPLS fits a blend with nothing observed in F or Z, and a material with nothing observed in D
(#513). The row's regression had an empty denominator, which gave NaN, and the NaN spread to
every score of every later component, so since #709 the fit was refused. The row now gets a
zero score or loading in that block and no weight in its regressions, as PCA and PLS fit an
all-missing row, so through that block it has no effect on the other rows. A fit still refused,
because a block has no variation for the iteration to follow, now says that rather than naming
empty rows. regress_a_space_on_b_row returns 0 rather than NaN for a row with nothing present.
TPLS super weights and block weights point the way of the super score (#513). The inner
PLS step's sign convention negated t, w and q but not u, and TPLS regresses its blocks on u, so on every component the convention flipped, w_loadings_super, r_loadings_f, s_loadings_d, v_loadings_d and w_loadings_z came out with the opposite sign to the super
score: with no Z block the F super weight was -1. Those components change sign now; scores,
predictions, SPE, Hotelling's T² and VIP do not change.
TPLS leaves a column with no variance out of every score (#513). Such a column is scaled to
NaN and zeroed, as it was, but its cells were still counted as observed, so in F they entered
every row's denominator and shrank every score; the warning added earlier said the column was
excluded when it was only partly so. The presence maps (not_na_d, not_na_f, not_na_z, not_na_y) are now taken after preprocessing, in fit and in diagnose. A fit whose F block has
a constant column now matches the same fit with that column unobserved; its scores, predictions
and loadings change.
TPLS.diagnose corrects a Z score for the row's missing cells, as fit does (#513). It
computed the correction and never applied it, so a row with a missing Z cell scored closer to 0 in diagnose than in fit, which moved its T², SPE and predictions too. Rows with every Z cell
observed are unchanged.
AnalysisRecipe and RecipeStep can be hashed, and their sequences cannot be changed in place
(#513). Both were frozen=True dataclasses holding lists, so hash(recipe) raised TypeError and recipe.stages.append(...) changed a registered recipe. cue_phrases, inputs_needed, stages and tools are now stored as tuples, whatever sequence is passed in; arg_hints stays a dict, copied
from the one passed in. to_payload() still returns lists. select_recipe no longer re-canonicalises
every cue phrase on every query.
Box-Behnken designs for 9, 10, 11, 12 and 16 factors are the published designs. They were built
from a two-level factorial in every pair of factors, which gives more runs than Box and Behnken's
(1960) designs: 144 instead of 120 runs at 9 factors, 180 instead of 160 at 10, 220 instead of 176 at
11, 264 instead of 192 at 12 and 480 instead of 384 at 16, before centre points. They now use the
incomplete block designs of the paper's Table 4, the 11-factor design with the half fraction x1 x2 x3 x4 x5 = +1 in each block of five. metadata["construction"] reports "published_blocks_of_three", "_four" or "_five"; 8, 13 to 15 and more than 16 factors, for which
no design was published, still pair every two factors ("all_pairs").
MCUVScaler keeps integer column labels. It took its labels from sklearn's feature_names_in_,
which sklearn sets only for string labels, so a frame with integer labels (a spectrum's wavelengths, or
a CSV read with header=None) came out labelled 0..K-1. The loadings of a model fitted to the scaled
frame were then labelled with the wrong wavelengths. center_, scale_, and the output of transform
and inverse_transform now carry the frame's own labels; ndarray input is still numbered from 0.
NIPALS PCA, PLS, MBPCA and MBPLS are right when a block has as many rows as columns. quick_regress chose whether to regress on the columns or the rows of a block from the block's shape,
so a square block (N = K, or N = M for Y) was regressed on the wrong axis. NIPALS PLS then gave wrong
weights, and NIPALS PCA wrong loadings, even on complete data; a cross-validation fold that happened
to be square gave a wrong Q2. quick_regress takes a new keyword per="column" or per="row", which
every NIPALS loop now passes. Called without it on a square block, it raises ValueError instead of
guessing; other shapes behave as before.
A factor named I no longer breaks a quadratic model with "'Series' object is not callable", nor a factor
named C an ASCA. Patsy finds a column before its own function of the same name, so a column I turned every
square term I(P ** 2) into a call of the column, and a column C did the same to ASCA's C(A, Sum). The formula
check now refuses a column named like a function the formula calls, with a rename to paste: .rename(columns={'I': 'I_'}). A factor I in a model without square terms still works. On factori.al, a design
whose model has squares (Box-Behnken, central composite, ...) refuses a factor named I when it is made, before
the runs, and offers I_.
factori.al refuses a factor named with a Python keyword, such as lambda, when you type it. The page
accepted the name, made the design and simulated the runs; only the analysis refused it, after the experiment.
analyze_experiment fits a response named I, C or Q. A column of that name hid patsy's function of
the same name, so a quadratic model failed with PatsyError: 'Series' object is not callable at I(x1 ** 2).
The response is now fitted under a stand-in name and reported under its own, as a response named after a
Python keyword already was. Box and Draper's Exercise 7.9 names its responses I, II and III.
Safe mode's memory cap no longer counts the imports against a tool call.max_memory_mb
(PROCESS_IMPROVE_MAX_MEMORY_MB) is now the address space a worker may map beyond what it mapped on importing
the package, as the worker's own documentation said. It was an absolute cap on RLIMIT_AS, which counts address
space reserved rather than memory in use: on the free-threaded build the imports alone reserve about 1.5 GiB, so
every tool that allocated failed with "out of memory", even on a 7 kB CSV. On the build with the GIL the imports
reserve about 0.8 GiB, so the total a worker may reserve rises from 1 GiB to about 1.8 GiB at the default.
The Holt-Winters ControlChart uses a given sigma when the data cannot estimate the scale. On a
series with no usable spread, such as one where every value is the same, calculate_limits(y, sigma=...)
raised with pinned lambdas or in the lambda search, although the error told the caller to pass sigma,
and ignored sigma and target below the warm-up length (fewer than 10 values), giving limits of zero
width (s = 0) or s = NaN for one value. Now sigma is the scale of the limits, the lambdas that the
data cannot choose are 0.5 and 0.8, and a short series uses a given target. Data with usable spread give
the same results as before. Without sigma, a short series with no spread now raises ValueError, so
the control_chart tool reports it as an error instead of charting it with zero-width limits.
Documentation
Correct f_sum See also reference (removed invalid f_cumsum).
Correct f_rupture See also reference (f_cross -> f_crossing).
Add cusum to the list of variants in ControlChart.calculate_limits.
Document the ok flag and early-exit shape of permutation_column_null.
Fix typo "elbox" -> "elbow" in f_elbow docstring.
robust_regression says which of its outputs are robust (#513). Only the slope and the
intercept are. The standard errors, t_value, R2, the confidence intervals and pi_range use the
least-squares formulas on the residuals around the robust line, so one outlier widens the intervals
without moving the slope. R2 is RegSS / (RegSS + RSS), which is not the 1 - RSS / TSS of multiple_linear_regression. The robust_regression tool's description says the same.
Each VIP says which variance weights its components (#513).vip() weights by R2X on a PCA
and by R2Y on a PLS, with one formula for both; MBPLS.block_vip_ weights by each block's R2X
while MBPLS.super_vip_ weights by R2Y; MBPCA.block_vip_ and TPLS.vip() weight by the block's
own variance. The docstrings now say so.
PCA.diagnose and PLS.diagnose say what shape their hotellings_t2 is (#513). PCA returns
the cumulative T² as a DataFrame with one column per component; PLS, MBPLS and MBPCA return the
full model's T² as a Series. Both docstrings now give each field's type and shape, and name the
difference: the Series is the DataFrame's last column.