rstudio / ggcheck Goto Github PK
View Code? Open in Web Editor NEWInspect ggplot2 Plots for Automated Grading in Learning Exercises
Home Page: https://pkgs.rstudio.com/ggcheck/
License: Other
Inspect ggplot2 Plots for Automated Grading in Learning Exercises
Home Page: https://pkgs.rstudio.com/ggcheck/
License: Other
tblcheck::grade_this_table()
and its related variants has been a phenomenally useful grading function that has become a go-to function among exercise writers. It performs a thorough comparison between the result of the students code and the result of the solution code, checking the various important ways that two tables can be different.
Can we provide something similar for grading plots?
grade_this_plot()
could begin by performing a few rudimentary checks that we know work well. This would allow exercise writers to quickly begin writing code with grade_this_plot()
. Over time, we could add more and better checks—and these would automatically be applied to exercises that use grade_this_plot()
without the exercise writers needing to revisit the templates (and template tests should keep us from breaking anything as we do).
This is arguable the "wild west" of ggplot2, so we can start simple by checking that code uses of one of the complete themes (e.g. theme_bw
vs theme_classic
). We can then expand later to individual element via theme()
.
At a minimum, we want to check for:
facet_wrap
vs facet_grid
)~ cyl
or drv ~ cyl
or nrow = 1
)https://github.com/rstudio/ggcheck/blob/main/R/is_ggplot.R#L73-L79
This means any .result
object will fail.
Having functions that take a list of aesthetics or labels in ...
is helpful interactively or when the number of items is small or well known. On the other hand, it's hard to programmatically construct calls to functions like uses_labels()
when one is working with an abstract (or unknown) number of labels/aesthetics/etc.
library(ggcheck)
library(ggplot2)
p <- ggplot(mtcars, aes(x = wt, y = mpg, color = cyl)) +
geom_point() +
labs(x = "weight", y = "MPG", color = NULL)
The following isn’t possible currently since we need arguments to ...
, but we could allow the first argument to be a list, in which case it becomes the starting structure for the internal args
list.
uses_labels(p, list(x = "weight", y = "MPG"))
#> Error: All inputs to `...` must be character vectors of length 1 or `NULL`.
Another option is to use rlang’s dynamic dots via rlang::list2()
to enable splicing.
uses_labels(p, !!!list(x = "weight", y = "MPG"))
#> Error in !list(x = "weight", y = "MPG"): invalid argument type
Still, the current version calls all()
on all of the labels/aesthetics, but from a programmatic perspective it might be better to return a named vector.
uses_labels(p, x = "weight", y = "MPG")
#> [1] TRUE
# may be easier in the long term to return a named logical vector
uses_labels(p, x = "weight", y = "MPG")
#> x y
#> TRUE TRUE
For example if a geom_qq
uses an x/y (for cases when you don't want one --- e.g. y axis for bar plot)
uses_mappings()
doesn't distinguish between a variable being mapped to an aesthetic and the name of the variable (as a string) being mapped:
library(ggplot2)
library(ggcheck)
p <-
ggplot(mpg, aes(x = cty, y = "hwy")) +
geom_point()
uses_mappings(p, mappings = aes(x = cty, y = hwy))
#> [1] TRUE
Created on 2021-10-14 by the reprex package (v0.3.0)
get_mappings()
captures the distinction:
get_mappings(p)
#> Aesthetic mapping:
#> * `x` -> `cty`
#> * `y` -> "hwy"
I think the issue is coming from identical_aes()
, since it converts all the aesthetic mappings to characters using rlang::as_name()
.
A ggplot layer is composed of a combination of geom and stat recorded in the ggplot object. This is nice for flexibility of creating a layer, but tricky for grading either one or a combination because names are not always mirrored between geoms and stats.
We need a system that maintains the proper default mapping between the two for a type of plot. A lookup table might help:
geom | GEOM | STAT |
---|---|---|
"qq" | "point" | "qq" |
"histogram" | "bar" | "bin" |
... | ... | ... |
A utility function can perform the lookup table, which can be used by a uses_geoms
or uses_stats
function for checking.
The one complication here is that when creating a layer using geom_
function vs creating a layer using stat_
function, the classes of the underlying geom/stat do not necessarily map. A possible workaround is to maintain another column, stat
so we can support a stat-centric table when creating layers using stat_
functions.
It might be worth checking out {gradethis} new checking flow to alleviate this problem by simply extracting out each geom/stat ahead of checking code.
It would be useful to be able to check for aesthetic parameters (i.e., aesthetics that have been set and not mapped), such as fill = "blue"
:
library(ggcheck)
library(ggplot2)
p <-
ggplot(mpg, aes(manufacturer)) +
geom_bar(fill = "blue", width = 0.5)
Created on 2021-11-16 by the reprex package (v2.0.1)
uses_geom_param()
checks "geom params", (binwidth
, etc.), but not "aes params" (fill = "blue"
):
uses_geom_param(p, "bar", params = list(fill = "blue"))
#> Error in uses_geom_param(p, "bar", params = list(fill = "blue")): Grading error: the supplied parameters 'fill' are invalid.
uses_geom_param(p, "bar", params = list(width = 0.5))
#> [1] TRUE
It would also be nice to be able to easily get aes params. You can do:
get_geom_layer(p, geom = "bar")$layer$aes_params
#> $fill
#> [1] "blue"
but it would be nice to have a function.
It might be useful to update the uses_label()
API to make it easier to test for the presence of user defined labels. Using the example from #22, unnamed arguments would simply be a test for presence of the label, not specifically comparing a value:
library(ggcheck)
library(ggplot2)
p <- ggplot(mtcars, aes(x = wt, y = mpg, color = cyl)) +
geom_point() +
labs(x = "weight", y = "MPG", color = NULL)
get_labels(p)
#> $x
#> [1] "weight"
#>
#> $y
#> [1] "MPG"
#>
#> $colour
#> NULL
uses_labels(p, "x")
#> [1] TRUE
uses_labels(p, x = "weight")
#> [1] TRUE
uses_labels(p, "fill")
#> [1] FALSE
uses_labels(p, "fill", "shape")
#> [1] FALSE FALSE
To start, we want to be able to check a couple of things about coordinates axes:
cord_cartesian
)xlim
/ylim
)@skaltman built some clever code to check for facets:
get_facet <- function(.result) {
stringr::str_remove(deparse(.result$facet$params$facets[[1]]), "~")
}
get_facet_params <- function(.result) {
.result$facet$params
}
ncol <- get_facet_params(.result)$ncol
if (!get_facet(.result) == "plant") {
fail("Did you remember to facet by `plant` using `facet_wrap()`?")
}
if (is.null(ncol)) {
fail("I expected you to use the `ncol` argument inside of `facet_wrap()` to match the layout in the example plot.")
}
if (ncol != 2) {
fail("I expected you to set the number of columns using the `ncol` argument inside of `facet_wrap()`.")
}
We should be able to create similar functions within ggcheck
for handling facets.
We would like to be able to check the values of parameters or somehow introspect the layer's params. For e.g. the geom_hist
accepts a binwidth
, and we'd like to check that they are using the correct value there. Another example is checking if a geom_boxplot
is using a specific outlier.alpha
(e.g. 0.01).
We would also like to be able check a layer created using a stat_
function, for example the stat_bin
for creating a geom_bar
where we can also grade the parameters like bins
.
Some things we want to check to start:
library(ggplot2)
library(ggcheck)
When a user misses a +
or has another small mistake in the snytax, the last value from running their code can end up being a ggplot2 component (layer, aesthetic, etc) but not the plot we expect.
.result <- local({
ggplot(mpg) +
aes(x = hwy, y = cyl) +
geom_point()
geom_smooth()
})
If you try to use ggcheck
functions right away, we run into an error.
uses_data(.result, mpg)
#> Error in UseMethod("get_data"): no applicable method for 'get_data' applied to an object of class "c('LayerInstance', 'Layer', 'ggproto', 'gg')"
We need a helper function to test that the output is a ggplot2 plot and to give useful feedback — did you forget a +
, etc. — without giving away the answer via code checking.
inherits(.result, "ggplot")
#> [1] FALSE
Created on 2021-11-05 by the reprex package (v2.0.1)
Add a default_param()
function to work like default_label()
with uses_geom_params()
.
Potential enhancement to #28.
Relevant comment: #28 (comment)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.