.conflicts.OK marker used to
suppress duplicate attachment-conflict messages, resolving an R-devel
check warning.gf_b(), with gf_coef() as the alias
readers who know coef() will look for. It annotates a
fitted model’s coefficients on the plot they describe: a continuous
predictor gets a rise-over-run triangle, with the rise labeled for the
run it spans (b[1] when that run is 1,
10 %*% b[1] when it is 10) and a hollow dot at
(0, b0); a categorical predictor gets a reference line at
the b0 group’s mean and one labeled arrow to each of the other groups;
the empty model gets the b0 line alone. Where gf_model()
draws the fit, gf_b() draws the numbers that describe it.
Every mark is placed from the model’s coefficients rather than from any
drawn point, so jitter never moves an arrow, and every mark is a
separately named layer, so a script can find one without counting
layers. show_b0 = TRUE, the default, expands the
predictor’s own axis to include 0 on a continuous model, because a
picture of b0 that does not show the predictor at 0 is not a picture of
b0. With no model, gf_b() reads the model the plot implies
and fits it at the call; on a faceted plot that is refused, because one
set of arrows drawn over a per-panel line would describe a fit no panel
actually has. Three shapes of model are refused rather than drawn,
because every mark is placed from the coefficients rather than from the
points and so nothing about drawing one would look wrong. A model whose
predictor the plot does not draw: lm(Thumb ~ Sex) over a
plot of five race groups, or lm(Thumb ~ log(Height)) over a
raw Height axis, where the triangle lands at 4.23 on an
axis running 59 to 76.5. A model with no intercept, which has no b0 for
the marks to be measured from. And a categorical predictor coded any way
but treatment – contr.sum, contr.helmert, an
ordered factor’s contr.poly – where coefficient k is not
group k’s difference from a reference group, which is the only thing an
arrow can mean. Choosing a different reference level is still fine: the
plot orders its groups by the same factor the model coded.gf_reduce() and gf_square_reduce(),
the latter also spelled gf_squareduce(): the third side of
the sum-of-squares decomposition. They draw the distance a model’s fit
moves each prediction away from the grand mean, so the reduction and the
residual – and their squares – put SS Model and SS Error on one picture
as lengths and as areas. Like a residual, a reduction runs along
whichever axis the plot puts the model’s outcome on, and starts on the x
each point is actually drawn at, jitter included. The grand mean is the
model’s own, taken from the rows it was fit on rather than off the
plot’s data, so on a faceted plot every panel is measured against the
same line and each panel draws a piece of one decomposition rather than
a decomposition of its own. aspect belongs to every square
layer on a plot or to none of them: “the reduction square plus the
residual square is the whole square” is a claim about areas on the page,
and it holds only while they all read the same one. A fit without an
intercept, or one fit with weights, is refused: the areas only add up
because an unweighted intercept leaves the residuals orthogonal to the
grand mean, and without that a reader counting squares is counting an
arithmetic that does not hold. Measured on
lm(Thumb ~ Height - 1), error plus reduction comes to
11700.01 against a total of 11880.21. gf_resid() still
measures either fit, needing no such identity.gf_model() called with no model draws the model the
plot implies. A numeric predictor draws the regression line, a
categorical predictor draws one mark at each group’s mean, and a plot of
an outcome alone draws the grand mean. ggformula::gf_lm()
draws nothing at all on a categorical x, and gf_model()
itself would only ever draw a model you had named. The inferred model is
fit per panel, which is the distinction worth knowing: a faceted
gf_point(body_mass_kg ~ flipper_length_m | species) %>% gf_model()
fits each species its own line, where naming a model above would repeat
that one whole-data fit in every panel. On the regression shape,
... reaches the fitting vocabulary gf_lm()
uses: se = TRUE draws the confidence band, n =
sets the prediction grid’s length, method.args = is
gf_lm()’s lm.args =, and
formula = y ~ poly(x, 2) fits a curve. A model’s line runs
across the data it was fit on and stops there, however wide the axis
around it gets. Inside that range every point the line interpolates has
observations on both sides of it; outside there are none, and saying the
pattern continues needs a theory or a physical constraint behind it
rather than a plot that happens to have room. So a
gf_lims(), or the b0 dot gf_b()
places at zero, leaves the line where it was.shuffle(Thumb) names a different permutation every time
it is evaluated, and independently in every layer that carries it, so
there is no seed to declare and nothing for a freshly fit model to agree
with. A gf_model() call with no model therefore pins the
drawn values onto the plot it returns – the same bargain
gf_resid() already makes for jitter. The plot you passed in
is untouched, and the returned plot still reads in your own words: its
axis titles, its refusals and its messages name
shuffle(Thumb), not the fixed column standing in for
it.shuffle(Thumb) ~ Height,
gf_model(Thumb ~ Height) computed the right predictions and
then left the outcome aesthetic unmapped, so the layer inherited the
plot’s own shuffle(Thumb) and ggplot2 re-evaluated it
against the prediction grid at draw time – a fresh permutation, in whose
order geom_line() then joined the predictions. The formula
names what to fit and the plot names what to show, so the plot’s outcome
expression is now evaluated once, at the call, and travels as a plain
column. A plain transformation such as sqrt() still lands
on the transformed axis. Neither the fix nor the pinning above moves
your random stream: drawing a model over a plot does not cost you a
sample.show_cutoffs() takes the distribution part directly, as
its second argument: show_cutoffs(p, middle(Thumb, .95)).
That is what every notebook wants to say, and until now the only way to
say it was to shade the plot with a matching fill first. An
explicit part overrides the fill rather than having to agree with it,
because marking the 99% cutoffs on a plot shaded for the 95% loses
nothing and is a normal thing to want. The argument is read by shape and
never by value, so show_cutoffs(p, "red") and
col <- "red"; show_cutoffs(p, col) are refused alike – a
color in a position that does not take one.show_cutoffs() refuses two plots it used to mark
meaninglessly: one whose first layer does not draw a distribution –
cutoffs describe where a distribution’s mass sits, and a scatterplot,
boxplot or violin has no mass to cut into – and, where the part is given
explicitly, a part naming a variable other than the one the plot puts on
x. The variable is compared by name and never evaluated, so a part
naming a column that does not exist is refused by name rather than
failing inside eval_tidy().show_cutoffs() twice stacks a second,
independent set of markers, and nothing about the first call has to
change for the second to land correctly. Labels are the one part of that
picture that does not stack: both sets are drawn at the same height, so
a second labels = TRUE call now warns that they will
overlap, and draws them anyway – the plot is still the one you asked
for, just harder to read.StatCutoff export: the cutoff rule as a
ggplot2::Stat, emitting the intercepts to pair with
ggplot2::GeomVline in a plot you are assembling yourself.
It calls the same function show_cutoffs() calls, so the two
cannot compute a different cutoff for the same distribution part. It
computes per panel, which is what a stat does with the rows ggplot2
hands it; show_cutoffs() keeps marking the whole
distribution, because that is what a middle() fill shades.
Its func and prop are checked, which the
show_cutoffs() route got for free by reading a call:
unchecked, func = "bogus" marked a lower cutoff and
prop = 2 marked the smallest observation as an upper one,
both indistinguishable on the page from a real mark. It plans in the
data’s own space and carries the result back, so a cutoff means the same
tail on scale_x_reverse() as anywhere else – a quantile
survives a transformation that increases and turns over under one that
decreases.gf_jitter() %>% gf_model() %>% gf_resid() drew every
point on its group’s center. Adding a residual to an unseeded jitter now
returns a plot whose jitter is fixed, so a second overlay lands on the
same dots; the plot you passed in is left as it was.gf_ function behaves – ggformula builds the
layer with parameter checking off – and gf_model(),
gf_sd_ruler(), gf_squareplot() and the five
residual functions are ordinary gf_ functions now, so they
behave that way too. The exception is the six arguments
gf_squareplot() itself used to take –
show_mean, show_dgp, xrange,
xbreaks, mincount and
auto_subdivide – each of which is refused by name and told
what draws it now. Left to fall through, a notebook written against the
old signature would keep running while the mark it asked for was simply
absent, which is worth answering. The list is bounded to the arguments
the function actually had and does not grow; ggplot2 keeps
size on the line geoms for the same reason. A genuine
misspelling is the ordinary story:
gf_squareplot(~Thumb, data = Fingers, binwidht = 5) draws a
plot that ignores it, while a correctly spelled neighbor on the same
call is applied. If a parameter appears to do nothing, check its
spelling first; ggplot2::ggplot_build(p)$data[[1]] shows
what the layer actually received.gf_squareplot() is now a real ggformula layer. It
carries the data it was given and maps x, so it facets with
~ x | group, accepts a data frame piped into it, takes a
mapped fill, can be added to a plot you already have, and
can be read by gf_model() and gf_sd_ruler() –
none of which worked before, because the function built an empty plot
and handed the layer a renamed one-column frame.
~log(Thumb) and any other expression now plots what it
says.gf_squareplot() bins the values that are actually
drawn. It used to choose the binwidth and the origin from the raw data
when the call was made, then hand them to a stat that runs after the
scales have transformed everything, so scale_x_log10() drew
four values spanning three orders of magnitude as a single column on an
axis running to 10^33. The stat decides now.gf_squareplot()’s scale and annotation arguments have
moved to the scales and annotations that own them: xrange
is gf_lims(x = ), xbreaks is
scale_x_continuous(breaks = ), mincount is
expand_limits(y = ), and show_mean and
show_dgp are %>% show_mean() and
%>% show_dgp(). auto_subdivide is gone
because what it did is now what happens anyway: it was the opt-in that
split a bin of more than 75 observations into sub-columns so the squares
stayed countable, and squares now stay countable at any size without
being asked. Passing one of the old names is refused, with the
replacement named.%>% gf_refine(coord_transform(y = "sqrt")) –
coord_trans() on ggplot2 3.5, which is the same coord under
its older name. Each square still spans exactly one count, so the
squares are drawn shorter the higher up the stack they sit – which is
the point: the distortion is what shows that the unit changes as the
scale climbs. The borders between squares are fitted per square rather
than once for the layer, so the compressed ones at the top are not
swallowed by their own outlines. A scale_y_*()
transformation is still refused, because a scale transforms the counts
before the squares are built and the squares would be drawn in one space
and labeled in another; the refusal now names the coord spelling your
ggplot2 exports instead of saying a transformed axis is impossible. A
discrete y scale is still refused, and now says why: there is no count
for a square to be one of.bins, binwidth, center,
boundary, closed, breaks and
pad all mean exactly what they mean on
gf_histogram(), because a squareplot now bins through the
same code a histogram does. The default grid moved as a result – bins
are centered on round numbers rather than starting on them – and a
maximum sitting on a bin edge no longer gets a column of its own past
the end of the data.bins, center, boundary,
closed and breaks are now named parameters of
gf_squareplot(), not merely accepted through
..., so they appear in its usage, in autocomplete, and in
the help sheet a bare gf_squareplot() call prints.gf_bar() positions its bars, instead of a
column of level numbers sitting half a step past the labels they belong
to. A factor with more than 51 levels no longer merges neighboring
levels into shared columns. A binning argument passed alongside a
discrete x – binwidth, bins,
center, boundary, closed,
breaks, pad – now warns that it has no effect,
rather than being silently discarded.color colors the bar on a squareplot, which is the only
thing it ever affected, and an explicit "black" is now
drawn black rather than silently redrawn as the default grey.print.gf_squareplot() method is gone.
It existed to swallow a warning the old constructor caused by building a
plot with no complete layer of its own; a real ggformula layer never
triggers that warning, so gf_squareplot() now prints like
any other plot.gf_squareplot().GeomSquareplot can draw a bin as the bar its squares
add up to, through a bars parameter taking
"none", "outline" or "solid",
with bar_color and bar_linewidth to style it.
All three are one layer on one set of bins, and the bar is derived from
the squares rather than binned again, so it cannot land anywhere the
squares did not. A solid bar shows its bin’s group composition too: a
mapped fill stacks in the bar the way it stacks in the
squares.show_mean() and show_dgp(): pipe a
plot of one distribution through them to mark its mean, or to frame it
with the process that generated it – the population model on a top axis,
the sample estimate below the plot, and a marker at the null hypothesis.
They describe a distribution rather than one particular way of drawing
one, so the same frame now goes on a gf_histogram() or a
gf_dotplot() of the same data, and each layer they add
carries a name so it can be found and changed afterwards. On a faceted
plot show_mean() draws each panel’s own mean.
show_dgp() raises the count axis to make room for the
population band rather than hanging it in the margin: countable squares
size the separator between them from the fraction of the panel they
fill, so a band drawn outside the panel silently redraws every square
39% taller.gf_sd_ruler() is now a real ggformula layer. It takes a
formula rather than y and x arguments –
gf_sd_ruler(Thumb ~ Height) where you used to write
gf_sd_ruler(y = Thumb, x = Height) – and with that comes
everything the other gf_ functions already had:
y ~ x | group faceting, data-first piping,
title=/xlab=/ylab=, and a plot
whose aesthetics live on a layer rather than on the plot. y
and x are names the layer no longer recognizes and are
ignored like any other; size still works and says to write
linewidth.gf_sd_ruler() placing the ruler where no
observations are drawn. On a categorical x it derived positions in order
of first appearance while the axis orders them alphabetically, so on
unbalanced data where = "median" put the ruler over one
group and the median over another.gf_sd_ruler() on a faceted plot now measures each
panel’s own data. It used to compute one ruler from the pooled data and
stamp the same segment into every panel, which is the one thing a facet
exists to avoid.gf_sd_ruler() measures the values the plot draws. A
transformed axis or a computed mapping such as ~log(Thumb)
used to be refused outright; both are now measured in the space they are
drawn in.gf_sd_ruler() draws one ruler per panel, so an
aesthetic mapped on the call – gf_sd_ruler(color = ~Sex) –
is refused and points at y ~ x | Sex. It used to be
discarded without a word.StatSdRuler export. gf_sd_ruler() now
draws through a real ggplot2 stat instead of computing the ruler by
hand, and is exported so you can put a standard deviation ruler into a
plot you are building yourself.gf_model() is now built the same way every other
gf_ layer is, so it behaves like one. Calling it with no
arguments prints its own help instead of reporting a missing argument,
and calling it on a plot with no model says which model it needs and
names both ways of giving one – a fitted lm() or
aov(), or the formula for one – rather than surfacing R’s
own missing-argument error. show.help = TRUE now prints
that same help on a plot that already has a model to check, rather than
running the check anyway and reporting its result instead of the help
you asked for. Everything it draws is unchanged.gf_model() draws a model whose predictor is
transformed. gf_model(lm(Thumb ~ log(Height))) over a plot
of Thumb ~ Height, and the same claim written in place as
gf_model(Thumb ~ log(Height)), were both refused as using
variables the plot does not have, because log(Height) was
compared against the plot’s columns as though it were the name of one.
The prediction grid is now built over the columns a term is made of,
which is what predict() needs, and drawn against the plot’s
own mapping. A transformed outcome is still refused, and now
says so in those terms rather than failing while computing
aesthetics.gf_model() refuses a one-sided formula by name.
gf_model(~flipper_length_m) used to fail inside
lm.fit() with incompatible dimensions; it now
says the model has no outcome and shows where to write one.gf_model() says at the call, rather than while drawing,
that a fit line or a group mark needs its outcome mapped by the plot
rather than by a layer underneath it. Those two shapes leave the
outcome’s axis free and inherit it, which is what lets a flipped plot
draw correctly without any orientation logic; when there is nothing to
inherit, ggplot2 used to report a missing y from deep
inside the build.gf_model() draws a group model as a plain mark at each
group mean rather than as an errorbar. An errorbar glyph reads as
uncertainty – a standard error, a confidence interval – to students who
are weeks away from meeting interval estimates, while a group model
claims a single value per group and says nothing about how sure of it
you should be. The mark is drawn at the same place, the same width and
the same color as before; what goes away is the eight-point path with
two identical caps around a zero-length stem, and the ymin,
ymax and flipped_aes columns that described an
interval that was never there. A group mark drawn with
alpha is now as translucent as you asked for, instead of
nearly twice as dark where the two identical caps overlapped.gf_model() now says a model’s outcome has to be
numeric, instead of letting lm() coerce it and report
NA/NaN/Inf in 'y'. The carefully worded refusal was already
written; it just sat after the fit, where nothing with a categorical
outcome could ever reach it. On the one path that did reach it – a
logical outcome, which lm() accepts – it reported the type
as “character”, because it read the class of the variable’s name rather
than of the variable.gf_model(size = ) no longer trips ggplot2’s
size-is-now-linewidth deprecation warning,
which told the reader that coursekata had done something wrong and asked
them to file an issue. The value was correctly folded into
linewidth and then also passed along under its old name,
where the geom accepted and discarded it. A size mapped by
the plot underneath also used to overwrite a linewidth
given explicitly to gf_model(); the explicit one now
wins.gf_model(), show_cutoffs() and the
residual overlays now read a plot through one reader, which finds a
variable whether it was mapped on the plot or on the plot’s first layer,
and finds the data there too. Plots built with ggformula always map at
plot level, so this changes nothing about the documented pipelines. For
show_cutoffs() and the residual overlays it changes how
they read rather than what they can draw: a plot written as
ggplot(data) + geom_point(aes(x, y)) already worked. What
it buys is gf_model(), which used to refuse that plot
outright and now draws an intercept model on it. A fit line or a group
mark still needs its outcome mapped on the plot rather than on a layer
beneath it – those two shapes leave the outcome’s axis free and inherit
it, so there is nothing for them to inherit from – and that is now
refused by name, at the call. The residual overlays’ refusal, for a plot
that really has no x or y anywhere, names the axis that is absent.gf_resid(),
gf_square_resid(), gf_squaresid(),
gf_resid_fun() and gf_square_resid_fun() – are
now built the same way every other gf_ layer is, which
closes the last place this package had two ways of making the same kind
of thing. Nothing they draw has changed. What arrives with it is the
rest of the family’s behavior: a bare call prints its own help,
title=/xlab=/ylab= reach the
plot, and a first argument that is not a plot says so rather than
failing with attempt to apply non-function. A call with no
model, or no function, names what it needs instead of reporting R’s own
missing-argument error.object, the
name every other gf_ function uses for it, and their
remaining arguments have to be named. The released signatures put
linewidth third on gf_resid(), and
aspect and alpha third and fourth on
gf_square_resid(); those positions belong to the arguments
every generated layer carries there, so write
gf_resid(p, model, linewidth = 0.5) and
gf_square_resid(p, model, aspect = 1). A value left in the
third position is refused by ggformula, in the words it uses for any
other gf_ function.gf_resid() and gf_square_resid() now
measure the residual along whichever axis the plot puts the model’s
outcome on. A plot of Thumb ~ Height with
lm(Height ~ Thumb) over it drew every segment vertically,
to a predicted height read off as though it were a thumb
length, with nothing to say so; the squares squared that same wrong
distance. The residual now runs across x for a model of the x variable,
its square turns with it, and the fitted end lands on the line
gf_model() draws for the same model.show_cutoffs() now draws its markers on a plot whose
count axis has been transformed. The triangles hang below the axis,
which means a negative count, and scale_y_sqrt() or
scale_y_log10() has no such value to offer: both markers
came out at NA and both dashed uprights lost their lower
end, with eight warnings, and the labels, which did survive, were placed
by treating a square root as a count and landed a third of the way up
the panel where they belong two thirds of the way up. None of those
heights was ever a count – they are fractions of the panel – so they are
now measured in the space the axis is drawn in and handed over as
positions no scale is asked to represent.show_cutoffs() on a plot that
already had markers moved them, because the second call measured the
axis the first one had stretched. On a coord_flip()
histogram it was worse – the heights were measured against the range of
the variable on the vertical axis rather than the counts, so the labels
were placed off the end of the count axis and the bars were redrawn at
under two thirds of their length. The reference figure for
middle(Thumb, .95) changes accordingly: the markers are in
the same place relative to the bars, and the empty band beneath the axis
is gone.middle(), tails(), and
outer() dropping one value from each tail when the tail’s
exact size is a whole number. middle(x, .90) on 20 values
highlighted all twenty instead of eighteen. The tail proportion is
computed as (1 - prop) / 2, which cannot represent .05
exactly, so the count landed a fraction below the whole number and
rounded down. prop = .95 was unaffected, which is why this
survived.library(coursekata) no longer attaches
fivethirtyeightdata, and the drat repository that existed
only to serve it is gone. No CourseKata teaching content uses any of its
19 exclusive datasets, and it accounted for roughly 63 MiB of the
browser-based Playground bundle – 93% of the cost of shipping every
suggested data package. fivethirtyeight itself is
unaffected; if you use a dataset that lived only in
fivethirtyeightdata, install it with
install.packages("fivethirtyeightdata", repos = "https://fivethirtyeightdata.github.io/drat/").coursekata is actually used.mosaic 1.10.2 or later, which raises the
minimum R version to 4.1. Earlier mosaic releases are
broken by rlang 1.2.0:
do(1000) * b1(shuffle(...)) returns the same value a
thousand times instead of a sampling distribution, with no error to warn
you. Since coursekata attaches mosaic for you,
an old copy meant silently wrong sampling distributions in your own
work.ggformula 0.12.0 or later. Two separate
reasons, both measured rather than assumed. On R 4.4 and later,
ggformula 0.10.1’s own
create_extras_and_dots() fails every gf_*
call, coursekata’s included, with
unique() applies only to vectors – a bug in
ggformula itself, fixed in 0.10.2. Beyond that,
coursekata now builds several layers with
ggformula::layer_factory(), handing it this package’s own
Stat and Geom objects; only 0.12.0 and later
capture those unevaluated, and earlier versions resolve them while
coursekata’s namespace is still being built, so the package
cannot be installed at all.do()
and shuffle(). They construct the same distributions with
base R, so a reference page cannot break because an attached package
changed the shape of what it returns. The full shuffle-and-estimate
workflow is now taught in the sampling
distributions guide, alongside a new model
visualization guide.gf_resid(), gf_square_resid(),
gf_resid_fun(), and gf_square_resid_fun()
overlays misaligning with jittered points: jitter positions are now
pinned to the plot itself, so they are stable across repeated builds and
chained calls, and the user’s random seed is no longer reset.gf_square_resid() drawing rectangles instead of
squares on jittered plots. The vertical side was measured from the
displayed (jittered) position, but the horizontal side was measured
against the model’s unjittered response, so the two sides disagreed by
the size of the jitter. The whole point of the plot is to show squared
error as area, so a shape that was not actually square undermined
it.geom_jitter() or
position = "jitter" shares one position object with every
other such layer in the session, so pinning it in place fixed the jitter
for unrelated plots too.gf_sd_ruler() now works on histograms, drawing a
horizontal ruler from the mean to mean + SD along the baseline.gf_sd_ruler() now reports which variable it could not
use, instead of failing inside sd() on a categorical
outcome or silently drawing nothing when handed a name that is not in
the data.gf_sd_ruler()’s y
and x arguments. Bare (unquoted) names previously errored
despite being documented, and a variable holding a column name stopped
resolving when that was fixed; all three spellings now work.gf_squaresid() is no longer deprecated: it remains a
fully supported alias of gf_square_resid(). With our
appreciation to Tyler Haslam (@TH4SL4M), the Utah high school teacher
whose efforts shaped the residual and squared-residual visualizations –
including working out how gf_resid() and
gf_square_resid() handle jitter plots, and the insight to
emphasize the area of the squares rather than their outline – and who
requested the function by this name.gf_resid() and gf_square_resid() no longer
refuse a model that was fit on fewer rows than the data holds.
lm() drops rows with missing values, and those are the same
rows the plot cannot draw a point for, so the two always agreed about
what is on screen – but the check counted rows before the plot dropped
them and reported 157 points against 128 fitted values for a plot that
draws 128 of each. The residual is now drawn for every point that is
drawn, and the error that remains is the one worth raising: the model
uses a variable the plot’s data does not have, named.gf_square_resid() and
gf_square_resid_fun() drawing every square in every facet
panel. The squares were built as a standalone table of coordinates that
carried none of the data’s other columns, so ggplot2 had nothing to
split them by and repeated all of them in each panel; their side was
also scaled against the first panel’s ranges wherever they landed. Each
square is now drawn where its observation is.StatResid, GeomResid and
GeomSquareResid exports. The residual overlays are real
ggplot2 layers now rather than a pre-rendered table of coordinates, so
they follow the plot they are added to – through facets, through a
jitter, and through rows the model dropped – and you can use them in a
plot you are assembling yourself.gf_model() failing with
'from' must be a finite number when a predictor on an axis
has any missing values. The grid of values the model is drawn over was
spanned between the predictor’s smallest and largest value without
excluding the gaps, so a single missing observation made both ends
undefined and nothing was drawn at all – including for a model
lm() had fit perfectly well on the rows that were
complete.gf_model() now errors when an aesthetic is mapped to a
variable that is not one of the model’s predictors, instead of silently
dropping the mapping. A mapping the model could not honor used to just
vanish from the plot without a word, which is a hard thing to debug in a
notebook.gf_squareplot() now names the variable it could not
use. A misspelled column, a data-first pipe, a character column and a
date column all reported the same `x` must be numeric., and
a formula holding an expression such as ~log(Thumb)
silently plotted the untransformed variable and labeled the axis with
it. na.rm = FALSE now says it is unsupported instead of
failing inside range() or shipping a rectangle at
NA. A two-sided formula such as
gf_squareplot(y ~ x) used to silently plot x
and discard y with no error; it now says the formula must
be one-sided.gf_squareplot() keeps drawing countable squares on
large samples. Above 75 observations in a bin it used to replace every
square with a solid bar, so the 2000-observation example in its own
documentation drew 27 bars and not one countable square. The separator
between squares is now fitted to the squares – at 2000 observations a
square is about 1.2 pt tall while the old separator was 1.4 pt wide, so
each square erased itself. A linewidth set explicitly on
the layer is still honored, now with a warning when the border is wide
enough to hide the observations behind it.StatSquareplot and GeomSquareplot
exports. gf_squareplot() now draws through a real ggplot2
stat and geom instead of assembling rectangles itself, and both are
exported so you can put countable squares into a plot you are building
yourself – including with a mapped fill, which stacks its
groups within each bin rather than drawing them on top of one
another.show_cutoffs() now reads its fill aesthetic the way R
reads any call. fill = ~middle(Thumb) was told it needed at
least two arguments even though prop is documented to
default to .95; named arguments in any other order, such as
~middle(prop = .9, x = Thumb) or
~middle(Thumb, greedy = FALSE, prop = .9), were still read
by position and failed on whatever landed in the third slot; and
~coursekata::middle(Thumb, .95) crashed on a length-3
coercion rather than being recognized as middle(). The call
is matched against the real function’s formals now, so naming arguments,
reordering them, leaving them at their documented defaults, and
qualifying the call with coursekata:: all behave as they do
everywhere else in R. greedy is honored too, having
previously been ignored.show_cutoffs() now puts its markers on values the fill
actually shades. The marker positions reimplemented the arithmetic that
decides how many observations fall in a tail rather than asking for it,
and the two disagreed whenever the tail’s exact size was a whole number:
with upper(x, .05) on 200 observations the triangle sat on
the 190th value while the shading started at the 191st. They come from
the same count now, so they cannot drift apart. The labels also compared
floating-point values against literals and never matched, so the
flagship example annotated its cutoffs 0.025 where a
textbook writes .025.show_cutoffs() now refuses a plot it cannot mark
instead of guessing at it. An x aesthetic holding an
expression rather than a bare variable surfaced a raw rlang
coercion error, and a plot without cartesian axes –
coord_polar(), say – fell back on an invented y axis
running to 30 and drew the markers at heights that meant nothing. Both
are named errors now, raised before any position is computed.gf_model() now documents the model you can write in
place. Alongside a model already fit by lm() or
aov(), it takes the formula for one –
gf_model(body_mass_kg ~ species) – and fits it against the
data the plot was built from, with body_mass_kg ~ NULL for
the empty model. That has always worked, and the help page promised
lm() or aov() only, so the shortest way to
draw a claim was also the least discoverable one. The outcome still has
to be named: ~species would describe predictors and no
claim, and the only way to draw it would be to guess the outcome off the
axes, which is what gf_lm() already does.gf_resid_fun(), gf_square_resid_fun(),
gf_sd_ruler(), gf_squareplot(),
show_cutoffs(), and outer().gf_squaresid() to gf_square_resid()
with deprecation warning for the old name.coursekata.check_missing option to control the
missing-package install prompt, with automatic suppression on
Emscripten/WASM environments.>= 3.5.2 to
support Emscripten/WASM environments.supernova when using default arguments.gf_resid() and gf_squaresid()
functions for residual and squared residual plots that layer onto
ggformula::gf_point() plots.Fingers$Gender to
Fingers dataset to prevent awkward naming in
exercises.pak. pak was
initially used to manage and parse dependencies, but itself depends on
curl. curl is not available on all platforms
(e.g. WASM), so we have removed the dependency on pak and
opted for pure R where possible (or remotes which has a
pure R fallback).FoodQuality to TipExperiment
dataset.rhub platforms, don’t run vdiffr tests on CI,
allow tests to run in parallel.Remotes field from
DESCRIPTIONvdiffr gave erroneous
resultsFingers.messy to
FingersMessypak::pkg_status() to improve startup
timerequire(lib.loc = ...) was
sometimes being passed NAgf_model_old() functioncoursekata.quiet option to suppress startup
messagescoursekata.quickstart and
coursekata.quiet options in READMEcoursekata.quickstart option, which can reduce load
times significantly.gf_model tests for density plots now that
upstream is fixed.test_fit() simple model stats to help teachers
evaluate student models.sse(), ssm(), ssr(),
SSE(), SSM(), SSR() functions:
they conflict with Metrics package.palmerpenguins and WorldMetrics
packagezarglefevdata$AGE values were truncated by first
character. This has been fixed.pak for package management.game_data dataset.Patch release to resolve R CMD CHECK failures.
size for line widths to
linewidth.