diff --git a/DESCRIPTION b/DESCRIPTION index 745d2dd8..140255f5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: plot2 Type: Package Title: Lightweight extension of base R plot -Version: 0.0.2.9010 +Version: 0.0.2.9011 Authors@R: c( person( diff --git a/NAMESPACE b/NAMESPACE index 73315489..5d76df8e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(plot2,default) S3method(plot2,density) S3method(plot2,formula) export(plot2) +importFrom(grDevices,adjustcolor) importFrom(grDevices,hcl.colors) importFrom(grDevices,hcl.pals) importFrom(grDevices,palette) diff --git a/NEWS.md b/NEWS.md index eac21f43..f193bac1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# plot2 0.0.2.9010 (development version) +# plot2 0.0.2.9011 (development version) Breaking changes: @@ -24,8 +24,8 @@ automatically adjusting plot characters and line types by groups. (#28 "top!", "left!", "topright!", etc. Users also gain finer control over many other aspects of the legend via the new unified `legend` argument, including changing labels, turning of the legend title, and so on. (#34 @grantmcdermott) -- Add support for `type="pointrange"` and `type="errobar"` plots (#35 -@vincentarelbundock and #40 @grantmcdermott) +- Add support for `"pointrange"`, `"errobar"`, and `"ribbon"` plot types. (#35 +@vincentarelbundock, #40 and #46 @grantmcdermott) - Support `grid = TRUE` as an alternative to `grid = grid()`. (#43 @grantmcdermott) diff --git a/R/by_aesthetics.R b/R/by_aesthetics.R index 2c904c19..b743b741 100755 --- a/R/by_aesthetics.R +++ b/R/by_aesthetics.R @@ -76,7 +76,7 @@ by_col = function(ngrps = 1L, col = NULL, palette = NULL) { by_pch = function(ngrps, type, pch=NULL) { no_pch = FALSE - if (!type %in% c("p", "b", "o", "pointrange")) { + if (!type %in% c("p", "b", "o", "pointrange", "errorbar")) { no_pch = TRUE pch = NULL @@ -116,7 +116,7 @@ by_pch = function(ngrps, type, pch=NULL) { by_lty = function(ngrps, type, lty=NULL) { # don't care about line type, return NULL - if (!type %in% c("l", "b", "o", "c", "h", "s", "S")) { + if (!type %in% c("l", "b", "o", "c", "h", "s", "S", "ribbon")) { out = NULL # special "by" convenience keyword diff --git a/R/plot2.R b/R/plot2.R index 5832e467..952c7b0e 100644 --- a/R/plot2.R +++ b/R/plot2.R @@ -29,8 +29,8 @@ #' lines, "o" for overplotted points and lines, "s" and "S" for stair steps #' and "h" for histogram-like vertical lines. "n" does not produce #' any points or lines. -#' - Additional plot2 types: "pointrange" draws point range plots and -#' "errorbar" draws error bar plots. +#' - Additional plot2 types: "pointrange", "errorbar", and "ribbon" for +#' drawing these respective plot types. #' @param xlim the x limits (x1, x2) of the plot. Note that x1 > x2 is allowed #' and leads to a ‘reversed axis’. The default value, NULL, indicates that #' the range of the `finite` values to be plotted should be used. @@ -117,7 +117,7 @@ #' @param ... other `graphical` parameters (see `par` and also the "Details" #' section of `plot`). #' -#' @importFrom grDevices palette palette.colors palette.pals hcl.colors hcl.pals +#' @importFrom grDevices adjustcolor palette palette.colors palette.pals hcl.colors hcl.pals #' @importFrom graphics axis box grconvertX lines par plot.new plot.window points title #' #' @examples @@ -274,8 +274,8 @@ plot2.default = function( if (is.null(xlab)) xlab = deparse(substitute(x)) if (is.null(ylab)) ylab = deparse(substitute(y)) - xlabs = NULL - if (type %in% c("pointrange", "errorbar")) { + xlabs = NULL + if (type %in% c("pointrange", "errorbar", "ribbon")) { if (is.character(x)) x = as.factor(x) if (is.factor(x)) { ## Need to maintain order that was observed in the original data @@ -510,10 +510,10 @@ plot2.default = function( # axes, plot.frame and grid if (axes) { - if (type %in% c("pointrange", "errorbar") && !is.null(xlabs)) { - axis(1, at = xlabs, labels = names(xlabs)) + if (type %in% c("pointrange", "errorbar", "ribbon") && !is.null(xlabs)) { + axis(1, at = xlabs, labels = names(xlabs)) } else { - axis(1) + axis(1) } axis(2) } @@ -526,6 +526,22 @@ plot2.default = function( } } + # polygons before lines + if (type == "ribbon") { + invisible( + lapply( + seq_along(split_data), + function(i) { + graphics::polygon( + x = c(split_data[[i]]$x, rev(split_data[[i]]$x)), + y = c(split_data[[i]]$ymin, rev(split_data[[i]]$ymax)), + col = adjustcolor(col[i], 0.2), + border = FALSE + ) + } + ) + ) + } ## segments/arrows before points if (type == "pointrange") { invisible( @@ -533,9 +549,9 @@ plot2.default = function( seq_along(split_data), function(i) { graphics::segments( - x0 = seq_along(split_data[[i]]$x), + x0 = split_data[[i]]$x, y0 = split_data[[i]]$ymin, - x1 = seq_along(split_data[[i]]$x), + x1 = split_data[[i]]$x, y1 = split_data[[i]]$ymax, col = col[i], lty = lty[i] @@ -544,15 +560,15 @@ plot2.default = function( ) ) } - if (type == "errorbar") { + if (type == "errorbar") { invisible( lapply( seq_along(split_data), function(i) { graphics::arrows( - x0 = seq_along(split_data[[i]]$x), + x0 = split_data[[i]]$x, y0 = split_data[[i]]$ymin, - x1 = seq_along(split_data[[i]]$x), + x1 = split_data[[i]]$x, y1 = split_data[[i]]$ymax, col = col[i], lty = lty[i], @@ -583,7 +599,8 @@ plot2.default = function( } ) ) - } else if (type %in% c("l", "o", "b", "c", "h", "s", "S")) { + } else if (type %in% c("l", "o", "b", "c", "h", "s", "S", "ribbon")) { + if (type=="ribbon") type = "l" invisible( lapply( seq_along(split_data), diff --git a/README.Rmd b/README.Rmd index 43425d3c..37225c2b 100644 --- a/README.Rmd +++ b/README.Rmd @@ -227,10 +227,10 @@ with( ) ``` -### Point range and error bar plots +### Point ranges, error bars, and ribbon plots -`plot2` adds supports for point range and error bar plots via the `"pointrange"` -and `"errorbar"` type arguments. An obvious use-case is for regression +`plot2` adds supports for uncertainty intervals via the `"pointrange"`, +`"errorbar"`, `"ribbon"` type arguments. An obvious use-case is for regression coefficient plots. ```{r pointrange, warning = FALSE} @@ -249,7 +249,7 @@ with( plot2( x = term, y = estimate, ymin = ci_low, ymax = ci_high, - type = "pointrange", + type = "pointrange", # or: "errobar", "ribbon" pch = 19, col = "dodgerblue", grid = TRUE, main = "Effect on Temperature" diff --git a/README.html b/README.html deleted file mode 100644 index d313806f..00000000 --- a/README.html +++ /dev/null @@ -1,888 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - - -A lightweight extension of the base R plot function,
-with support for automatic grouping and legend handling, and several
-other enhancements.
plot2 is not yet on CRAN, but can be installed from -R-universe.
-install.packages("plot2", repos = "https://grantmcdermott.r-universe.dev")Our goal is to submit to CRAN by the end of July/August 2023, once we -have settled on some remaining design choices and features support. You -can take a look at the open issues to -see what’s currently under consideration. Please feel free to weigh on -these if you have opinions. We want end users to have a say in -determining the final product.
-R users are spoiled for choice when it comes to visualization -frameworks. The options of course include ggplot2 -(arguably the most important graphics system of the last decade) and -lattice, not to mention a bewildering array of -extensions built around, on top of, and in between these amazing -packages.
-It is perhaps not surprising, then, that the base R graphics system
-sometimes gets short shrift. This is unfortunate, because base R offers
-very powerful and flexible plotting facilities. Just type
-demo(graphics) or demo(persp) into your R
-console to get an idea. Or, take a look at these
-two
-excellent tutorials. The downside of this power and flexibility is that
-base R plotting can require a fair bit of manual tinkering. A case in
-point is plotting grouped data with an appropriate legend. Doing so with
-the generic plot() function can require several function
-calls or a loop, fiddling with your plot regions, and then generating
-the legend manually.
The plot2 package aims to remove this overhead. It
-provides a lightweight extension of the base R plot system with various
-convenience features, particularly for creating (2D) scatter and line
-plots using grouped data. For example, the core plot2()
-function makes it easy to plot different categories of a dataset in a
-single function call and highlight these categories (groups) using
-modern colour palettes. Coincident with this grouping support,
-plot2() also produces automatic legends with scope for
-further customization. While the package offers several other
-enhancements, it tries as far as possible to be a drop-in replacement
-for the equivalent base plot function. Users should generally be able to
-swap a valid plot() call with plot2() without
-any changes to the expected output.
Let’s load the package then walk through some examples.
-library(plot2)plot()As far as possible, plot2 tries to be a drop-in
-replacement for regular plot calls.
par(mfrow = c(1, 2))
-
-plot(0:10, main = "plot")
-plot2(0:10, main = "plot2")Similarly, we can plot elements from a data frame using either the -atomic or formula methods.
-par(mfrow = c(2, 2))
-
-plot(airquality$Day, airquality$Temp, main = "plot")
-plot(Temp ~ Day, data = airquality, main = "plot (formula)")
-plot2(airquality$Day, airquality$Temp, main = "plot2")
-plot2(Temp ~ Day, data = airquality, main = "plot2 (formula)")
-dev.off() # reset to default (single) plot window
-#> null device
-#> 1So far, so good. But where plot2 starts to diverge from
-its base counterpart is with respect to grouped data. In particular,
-plot2 allows you to characterize groups using the
-by argument.1
# plot2(airquality$Day, airquality$Temp, by = airquality$Month) # same as below
-with(airquality, plot2(Day, Temp, by = Month))An even more convenient approach is to use the equivalent formula
-syntax. Just place the grouping variable after a vertical bar (i.e.,
-|).
plot2(Temp ~ Day | Month, data = airquality)You can use standard base plotting arguments to adjust features of
-your plot. For example, change pch (plot character) to get
-filled points.
plot2(
- Temp ~ Day | Month,
- data = airquality,
- pch = 16
-)Similarly, converting to a grouped line plot is a simple matter of
-adjusting the type argument.
plot2(
- Temp ~ Day | Month,
- data = airquality,
- type = "l"
-)Note that we can automatically adjust both pch and
-lty by groups using the "by" convenience
-keyword. This can be used in conjunction with the default group
-colouring. Or, as a replacement for group colouring—an option that may
-be particularly useful for contexts where colour is expensive or
-prohibited (e.g., certain academic journals).
plot2(
- Temp ~ Day | Month,
- data = airquality,
- type = "l",
- col = "black", # override automatic group colours
- lty = "by" # change line type by group instead
-)On the subject of group colours, these are easily customized via the
-palette argument. The default group colours are inherited
-from either the “R4” or “Viridis” palettes, depending on the number of
-groups. However, all of the many palettes listed by
-palette.pals() and hcl.pals() are supported as
-convenience strings.2 For example:
plot2(
- Temp ~ Day | Month,
- data = airquality,
- type = "l",
- palette = "Tableau 10" # or "ggplot2", "Okabe-Ito", "Set 2", "Harmonic", etc.
-)Beyond these convenience strings, users can also supply a valid -palette-generating function for finer control over transparency, colour -order, and so forth. We’ll see a demonstration of this further -below.
-In all of the preceding plots, you will have noticed that we get an
-automatic legend. The legend position and look can be customized using
-appropriate arguments. You can change (or turn off) the legend title and
-bounding box, switch the direction of the legend text, etc. Below, we
-particularly draw your attention to the trailing “!” in the
-legend position argument. This tells plot2 to
-place the legend outside the plot area.
plot2(
- Temp ~ Day | Month,
- data = airquality,
- type = "l",
- legend = legend("bottom!", title = "Month of the year", bty = "o")
-)Note that legend position keywords without the exclamation point -(i.e., for inside the plot area) should still as per normal. Grouped -density plot example:
-with(
- airquality,
- plot2(
- density(Temp),
- by = Month,
- legend = legend("topright", bty = "o")
- )
-)plot2 adds supports for point range and error bar plots
-via the "pointrange" and "errorbar" type
-arguments. An obvious use-case is for regression coefficient plots.
aq = airquality
-aq$mnth = factor(month.abb[aq$Month], levels = month.abb)
-mod = lm(Temp ~ 0 + mnth, aq)
-coeftab = data.frame(
- gsub("mnth", "", names(coef(mod))),
- coef(mod),
- confint(mod)
- ) |>
- setNames(c("term", "estimate", "ci_low", "ci_high"))
-
-with(
- coeftab,
- plot2(
- x = term, y = estimate,
- ymin = ci_low, ymax = ci_high,
- type = "pointrange",
- pch = 19, col = "dodgerblue",
- grid = TRUE,
- main = "Effect on Temperature"
- )
-)Customizing your plots further is straightforward, whether that is
-done by changing global parameters (via par) or invoking
-plot2 arguments. Here’s a quick penultimate example, where
-we change our point character, tick labels, and font family globally,
-before adding some transparency to our colour palette, and use
-Tufte-style floating axes with a background panel grid.
par(
- pch = 16, # Filled points as default
- las = 1, # Horizontal axis tick labels
- family = "HersheySans" # Use a (built-in) Hershey font instead of Arial default
-)
-
-plot2(
- Temp ~ Day | Month,
- data = airquality,
- type = "b",
- palette = palette.colors(palette = "Tableau 10", alpha = 0.5),
- main = "Daily temperatures by month",
- frame = FALSE, grid = TRUE
-)At the risk of repeating ourselves, the use of par in
-the previous example again underscores the correspondence with the base
-graphics system. Because plot2 is effectively a convenience
-wrapper around base plot, any global elements that you have
-set for the latter should carry over to the former. For nice
-out-of-the-box themes, we recommend the basetheme
-package.
par(pch = 15, las = 0, family = "") # change/revert global changes from above
-
-library(basetheme)
-basetheme("royal") # or "clean", "dark", "ink", "brutal", etc.
-
-plot2(
- Temp ~ Day | Month,
- data = airquality,
- type = "b",
- pch = "by",
- palette = "Tropic",
- main = "Daily temperatures by month"
-)
-basetheme(NULL) # back to default theme
-dev.off()
-#> null device
-#> 1In summary, consider the plot2 package if you are
-looking for base R plot functionality with some added
-convenience features. You can use pretty much the same syntax and all of
-your theming elements should carry over too. It has no dependencies
-other than base R itself and this makes it an attractive option for
-situations where dependency management is expensive (e.g., an R
-application running in a browser via WebAssembly).
At this point, experienced base plot users might protest
-that you can colour by groups using the col
-argument, e.g.
-with(airquality, plot(Day, Temp, col = Month)). This is
-true, but there are several limitations. First, you don’t get an
-automatic legend. Second, the base plot.formula method
-doesn’t specify the grouping within the formula itself (not a
-deal-breaker, but not particularly consistent either). Third, and
-perhaps most importantly, this grouping doesn’t carry over to line plots
-(i.e., type=“l”). Instead, you have to transpose your data and use
-matplot. See this
-old StackOverflow thread for a longer discussion.↩︎
See the accompanying help pages of those two functions -for more details on the available palettes, or read the article by Achim Zeileis -and Paul Murrell.↩︎
-### Point range and error bar plots
+### Point ranges, error bars, and ribbon plots
-`plot2` adds supports for point range and error bar plots via the
-`"pointrange"` and `"errorbar"` type arguments. An obvious use-case is
-for regression coefficient plots.
+`plot2` adds supports for uncertainty intervals via the `"pointrange"`,
+`"errorbar"`, `"ribbon"` type arguments. An obvious use-case is for
+regression coefficient plots.
``` r
aq = airquality
@@ -257,7 +257,7 @@ with(
plot2(
x = term, y = estimate,
ymin = ci_low, ymax = ci_high,
- type = "pointrange",
+ type = "pointrange", # or: "errobar", "ribbon"
pch = 19, col = "dodgerblue",
grid = TRUE,
main = "Effect on Temperature"
diff --git a/man/plot2.Rd b/man/plot2.Rd
index 8798d59e..d1c7ebc6 100644
--- a/man/plot2.Rd
+++ b/man/plot2.Rd
@@ -108,8 +108,8 @@ for lines, "b" for both points and lines, "c" for empty points joined by
lines, "o" for overplotted points and lines, "s" and "S" for stair steps
and "h" for histogram-like vertical lines. "n" does not produce
any points or lines.
-\item Additional plot2 types: "pointrange" draws point range plots and
-"errorbar" draws error bar plots.
+\item Additional plot2 types: "pointrange", "errorbar", and "ribbon" for
+drawing these respective plot types.
}}
\item{xlim}{the x limits (x1, x2) of the plot. Note that x1 > x2 is allowed