diff --git a/NEWS.md b/NEWS.md
index e50f2f05..a63a7c42 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,4 +1,4 @@
-# plot2 0.0.2.9006 (development version)
+# plot2 0.0.2.9007 (development version)
Breaking changes:
@@ -16,7 +16,8 @@ New features:
- Both the `pch` and `lty` arguments now accept a "by" convenience keyword for
automatically adjusting plot characters and line types by groups (#28,
@grantmcdermott).
-- Point-range plots with `type="pointrange"` (#35 @vincentarelbundock)
+- Add support for `type="pointrange"` and `type="errobar"` plots (#35
+@vincentarelbundock and #40 @grantmcdermott)
Bug fixes:
diff --git a/R/plot2.R b/R/plot2.R
index e9599310..eae42e56 100644
--- a/R/plot2.R
+++ b/R/plot2.R
@@ -22,12 +22,14 @@
#' not be specified in the same call.
#' @param data a data.frame (or list) from which the variables in formula
#' should be taken. A matrix is converted to a data frame.
-#' @param type 1-character string giving the type of plot desired. The
-#' following values are possible, for details, see plot: "p" for points, "l"
+#' @param type character string giving the type of plot desired. Options are:
+#' - The same set of 1-character values supported by plot: "p" for points, "l"
#' 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. "pointrange" draws point-range plots.
+#' any points or lines.
+#' - Additional plot2 types: "pointrange" draws point range plots and
+#' "errorbar" draws error bar plots.
#' @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.
@@ -256,12 +258,27 @@ plot2.default = function(
if (is.null(ylab)) ylab = deparse(substitute(y))
if (is.null(legend.args$title)) ltitle = deparse(substitute(by))
+ xlabs = NULL
+ if (type %in% c("pointrange", "errorbar")) {
+ if (is.character(x)) x = as.factor(x)
+ if (is.factor(x)) {
+ ## Need to maintain order that was observed in the original data
+ ## (i.e., no new sorting by factor)
+ xlvls = unique(x)
+ x = factor(x, levels = xlvls)
+ xlabs = seq_along(xlvls)
+ names(xlabs) = xlvls
+ x = as.integer(x)
+ }
+ }
+
if (is.null(xlim)) xlim = range(x, na.rm = TRUE)
if (is.null(ylim)) ylim = range(y, na.rm = TRUE)
if (!is.null(ymin)) ylim[1] = min(c(ylim, ymin))
if (!is.null(ymax)) ylim[2] = max(c(ylim, ymax))
+
if (!is.null(by)) {
l = list(x=x, y=y)
l[["ymin"]] = ymin
@@ -401,14 +418,18 @@ plot2.default = function(
# axes, plot.frame and grid
if (axes) {
- axis(1)
+ if (type %in% c("pointrange", "errorbar") && !is.null(xlabs)) {
+ axis(1, at = xlabs, labels = names(xlabs))
+ } else {
+ axis(1)
+ }
axis(2)
}
if (frame.plot) box()
if (!is.null(grid)) grid
- # draw the points/lines
- if (type %in% "pointrange") { # segments before point
+ ## segments/arrows before points
+ if (type == "pointrange") {
invisible(
lapply(
seq_along(split_data),
@@ -417,13 +438,37 @@ plot2.default = function(
x0 = seq_along(split_data[[i]]$x),
y0 = split_data[[i]]$ymin,
x1 = seq_along(split_data[[i]]$x),
- y1 = split_data[[i]]$ymax
+ y1 = split_data[[i]]$ymax,
+ col = col[i],
+ lty = lty[i]
+ )
+ }
+ )
+ )
+ }
+ if (type == "errorbar") {
+ invisible(
+ lapply(
+ seq_along(split_data),
+ function(i) {
+ graphics::arrows(
+ x0 = seq_along(split_data[[i]]$x),
+ y0 = split_data[[i]]$ymin,
+ x1 = seq_along(split_data[[i]]$x),
+ y1 = split_data[[i]]$ymax,
+ col = col[i],
+ lty = lty[i],
+ length = 0.05,
+ angle = 90,
+ code = 3
)
}
)
)
}
- if (type %in% c("p", "pointrange")) {
+
+ ## now draw the points/lines
+ if (type %in% c("p", "pointrange", "errorbar")) {
invisible(
lapply(
seq_along(split_data),
diff --git a/README.Rmd b/README.Rmd
index bf33d5e9..13df6d65 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -227,21 +227,25 @@ with(airquality, plot2(
))
```
-### Point-range
+### Point range and error bar plots
-`plot2` adds a new `type="pointrange"` option to draw point-ranges 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.
```{r pointrange, warning = FALSE}
-mod = lm(mpg ~ hp + factor(cyl), mtcars)
+mod = lm(Temp ~ 0 + factor(Month), airquality)
coefs = data.frame(names(coef(mod)), coef(mod), confint(mod))
-coefs = setNames(coefs, c("x", "y", "ymin", "ymax"))
-with(coefs,
- plot2(pch = 17,
- x = 1:4,
- y = y,
- ymin = ymin,
- ymax = ymax,
- type = "pointrange"
+coefs = setNames(coefs, c("term", "estimate", "ci_low", "ci_high"))
+
+with(
+ coefs,
+ plot2(
+ x = term, y = estimate,
+ ymin = ci_low, ymax = ci_high,
+ type = "pointrange",
+ pch = 19,
+ main = "Effect on Temperature"
)
)
```
diff --git a/README.md b/README.md
index 75112c7a..840247f4 100644
--- a/README.md
+++ b/README.md
@@ -234,22 +234,25 @@ with(airquality, plot2(
-### Point-range
+### Point range and error bar plots
-`plot2` adds a new `type="pointrange"` option to draw point-ranges
-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.
``` r
-mod = lm(mpg ~ hp + factor(cyl), mtcars)
+mod = lm(Temp ~ 0 + factor(Month), airquality)
coefs = data.frame(names(coef(mod)), coef(mod), confint(mod))
-coefs = setNames(coefs, c("x", "y", "ymin", "ymax"))
-with(coefs,
- plot2(pch = 17,
- x = 1:4,
- y = y,
- ymin = ymin,
- ymax = ymax,
- type = "pointrange"
+coefs = setNames(coefs, c("term", "estimate", "ci_low", "ci_high"))
+
+with(
+ coefs,
+ plot2(
+ x = term, y = estimate,
+ ymin = ci_low, ymax = ci_high,
+ type = "pointrange",
+ pch = 19,
+ main = "Effect on Temperature"
)
)
```
diff --git a/inst/tinytest/_tinysnapshot/pointrange_errorbar.svg b/inst/tinytest/_tinysnapshot/pointrange_errorbar.svg
new file mode 100644
index 00000000..e696550e
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/pointrange_errorbar.svg
@@ -0,0 +1,73 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/readme_pointrange.svg b/inst/tinytest/_tinysnapshot/readme_pointrange.svg
new file mode 100644
index 00000000..bb73de3e
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/readme_pointrange.svg
@@ -0,0 +1,70 @@
+
+
diff --git a/inst/tinytest/test-README.R b/inst/tinytest/test-README.R
index 998659bf..288e9f3a 100644
--- a/inst/tinytest/test-README.R
+++ b/inst/tinytest/test-README.R
@@ -100,6 +100,23 @@ f = function() {
}
expect_snapshot_plot(f, label = "readme_palette_tableau")
+f = function() {
+ mod = lm(Temp ~ 0 + factor(Month), airquality)
+ coefs = data.frame(names(coef(mod)), coef(mod), confint(mod))
+ coefs = setNames(coefs, c("term", "estimate", "ci_low", "ci_high"))
+ with(
+ coefs,
+ plot2(
+ x = term, y = estimate,
+ ymin = ci_low, ymax = ci_high,
+ type = "pointrange",
+ pch = 19,
+ main = "Effect on Temperature"
+ )
+ )
+}
+expect_snapshot_plot(f, label = "readme_pointrange")
+
f = function() {
par(pch = 16, family = "HersheySans")
diff --git a/inst/tinytest/test-pointrange.R b/inst/tinytest/test-pointrange.R
index dfbb5e47..a67f747d 100644
--- a/inst/tinytest/test-pointrange.R
+++ b/inst/tinytest/test-pointrange.R
@@ -5,15 +5,31 @@ mod = lm(mpg ~ hp + factor(cyl), mtcars)
coefs = data.frame(names(coef(mod)), coef(mod), confint(mod))
coefs = setNames(coefs, c("x", "y", "ymin", "ymax"))
+fun = function() {
+ with(
+ coefs,
+ plot2(
+ pch = 17,
+ x = 1:4,
+ y = y,
+ ymin = ymin,
+ ymax = ymax,
+ type = "pointrange"
+ )
+ )
+}
+expect_snapshot_plot(fun, label = "pointrange_triangle")
+
fun = function() {
with(
coefs,
plot2(
- pch = 17,
- x = 1:4,
- y = y,
- ymin = ymin,
- ymax = ymax,
- type = "pointrange"))
+ x = x,
+ y = y,
+ ymin = ymin,
+ ymax = ymax,
+ type = "errorbar"
+ )
+ )
}
-expect_snapshot_plot(fun, label = "pointrange_triangle")
\ No newline at end of file
+expect_snapshot_plot(fun, label = "pointrange_errorbar")
\ No newline at end of file
diff --git a/man/figures/README-pointrange-1.png b/man/figures/README-pointrange-1.png
index d9c24454..7ad75e22 100644
Binary files a/man/figures/README-pointrange-1.png and b/man/figures/README-pointrange-1.png differ
diff --git a/man/plot2.Rd b/man/plot2.Rd
index 265e36c1..6dfcef86 100644
--- a/man/plot2.Rd
+++ b/man/plot2.Rd
@@ -106,12 +106,14 @@ the plot by.}
\item{data}{a data.frame (or list) from which the variables in formula
should be taken. A matrix is converted to a data frame.}
-\item{type}{1-character string giving the type of plot desired. The
-following values are possible, for details, see plot: "p" for points, "l"
+\item{type}{character string giving the type of plot desired. Options are:
+ - The same set of 1-character values supported by plot: "p" for points, "l"
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. "pointrange" draws point-range plots.}
+any points or lines.
+- Additional plot2 types: "pointrange" draws point range plots and
+"errorbar" draws error bar plots.}
\item{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