Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Point-range plots with type="pointrange" #35

Merged
merged 5 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: plot2
Type: Package
Title: Lightweight extension of base R plot
Version: 0.0.2.9006
Version: 0.0.2.9007
Authors@R:
c(
person(
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ 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)

Bug fixes:

Expand Down
2 changes: 1 addition & 1 deletion R/by_aesthetics.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,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")) {
if (!type %in% c("p", "b", "o", "pointrange")) {
no_pch = TRUE
pch = NULL

Expand Down
41 changes: 33 additions & 8 deletions R/plot2.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
#' following values are possible, for details, see 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. Finally, "n" does not produce
#' any points or lines.
#' and "h" for histogram-like vertical lines. "n" does not produce
#' any points or lines. "pointrange" draws point-range 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.
Expand Down Expand Up @@ -94,6 +94,7 @@
#' be calling `dev.off()` to reset all `par` settings to their defaults.)
#' @param subset,na.action,drop.unused.levels arguments passed to `model.frame`
#' when extracting the data from `formula` and `data`.
#' @param ymin,ymax minimum and maximum coordinates of the point range. Only used when `type="pointrange"`.
#' @param ... other `graphical` parameters (see `par` and also the "Details"
#' section of `plot`).
#'
Expand Down Expand Up @@ -241,6 +242,8 @@ plot2.default = function(
col = NULL,
lty = NULL,
par_restore = FALSE,
ymin = NULL,
ymax = NULL,
...) {

if (is.null(y)) {
Expand All @@ -255,12 +258,18 @@ plot2.default = function(

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)) {
split_data = lapply(list(x=x, y=y), split, by)
l = list(x=x, y=y)
l[["ymin"]] = ymin
l[["ymax"]] = ymax
split_data = lapply(l, split, by)
split_data = do.call(function(...) Map("list", ...), split_data)
} else {
split_data = list(list(x=x, y=y))
split_data = list(list(x=x, y=y, ymin = ymin, ymax = ymax))
}

ngrps = length(split_data)
Expand Down Expand Up @@ -399,7 +408,22 @@ plot2.default = function(
if (!is.null(grid)) grid

# draw the points/lines
if (type == "p") {
if (type %in% "pointrange") { # segments before point
invisible(
lapply(
seq_along(split_data),
function(i) {
graphics::segments(
x0 = seq_along(split_data[[i]]$x),
y0 = split_data[[i]]$ymin,
x1 = seq_along(split_data[[i]]$x),
y1 = split_data[[i]]$ymax
)
}
)
)
}
if (type %in% c("p", "pointrange")) {
invisible(
lapply(
seq_along(split_data),
Expand All @@ -415,8 +439,7 @@ plot2.default = function(
}
)
)
}
if (type %in% c("l", "o", "b", "c", "h", "s", "S")) {
} else if (type %in% c("l", "o", "b", "c", "h", "s", "S")) {
invisible(
lapply(
seq_along(split_data),
Expand All @@ -432,6 +455,8 @@ plot2.default = function(
}
)
)
} else {
stop("`type` argument not supported.", call. = FALSE)
}

title(
Expand Down
31 changes: 31 additions & 0 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Let's load the package then walk through some examples.
library(plot2)
```

### Similarity to `plot()`

As far as possible, `plot2` tries to be a drop-in replacement for regular `plot`
calls.

Expand All @@ -103,6 +105,8 @@ plot2(Temp ~ Day, data = airquality, main = "plot2 (formula)")
dev.off() # reset to default (single) plot window
```

### Grouped data

So 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.^[At this point, experienced base plot users
Expand Down Expand Up @@ -168,6 +172,8 @@ plot2(
)
```

### Colors

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
Expand All @@ -190,6 +196,8 @@ 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.

### Legend

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,
Expand Down Expand Up @@ -219,6 +227,27 @@ with(airquality, plot2(
))
```

### Point-range

`plot2` adds a new `type="pr"` option to draw point-ranges plots:
vincentarelbundock marked this conversation as resolved.
Show resolved Hide resolved

```{r pointrange}
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"))
with(coefs,
plot2(pch = 17,
x = 1:4,
y = y,
ymin = ymin,
ymax = ymax,
type = "pr"
)
)
```

### Customization

Customizing your plots further is straightforward, whether that is done by
changing global parameters or invoking `plot2` arguments. Here's a quick
penultimate example, where we change our point character and font family
Expand Down Expand Up @@ -266,6 +295,8 @@ basetheme(NULL) # back to default theme
dev.off()
```

## Conclusion

In 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
Expand Down
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ Let’s load the package then walk through some examples.
library(plot2)
```

### Similarity to `plot()`

As far as possible, `plot2` tries to be a drop-in replacement for
regular `plot` calls.

Expand Down Expand Up @@ -105,6 +107,8 @@ dev.off() # reset to default (single) plot window
#> 1
```

### Grouped data

So 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]
Expand Down Expand Up @@ -170,11 +174,13 @@ plot2(

<img src="man/figures/README-by_lty-1.png" width="100%" />

### Colors

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:
`hcl.pals()` are supported as convenience strings.\[2\] For example:
vincentarelbundock marked this conversation as resolved.
Show resolved Hide resolved

``` r
plot2(
Expand All @@ -191,11 +197,13 @@ 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.

### Legend

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
particularly draw your attention to the trailing “\!” in the
`legend.position` argument. This tells `plot2` to place the legend
*outside* the plot area.

Expand Down Expand Up @@ -226,6 +234,29 @@ with(airquality, plot2(

<img src="man/figures/README-desnity_topright-1.png" width="100%" />

### Point-range

`plot2` adds a new `type="pr"` option to draw point-ranges plots:

``` r
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"))
with(coefs,
plot2(pch = 17,
x = 1:4,
y = y,
ymin = ymin,
ymax = ymax,
type = "pr"
)
)
```

<img src="man/figures/README-pointrange-1.png" width="100%" />

### Customization

Customizing your plots further is straightforward, whether that is done
by changing global parameters or invoking `plot2` arguments. Here’s a
quick penultimate example, where we change our point character and font
Expand Down Expand Up @@ -283,6 +314,8 @@ dev.off()
#> 1
```

## Conclusion

In 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
Expand All @@ -291,7 +324,7 @@ makes it an attractive option for situations where dependency management
is expensive (e.g., an R application running in a browser via
[WebAssembly](https://docs.r-wasm.org/webr/latest/)).

[^1]: At this point, experienced base plot users might protest that you
1. At this point, experienced base plot users might protest that you
vincentarelbundock marked this conversation as resolved.
Show resolved Hide resolved
*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
Expand All @@ -304,7 +337,7 @@ is expensive (e.g., an R application running in a browser via
[this](https://stackoverflow.com/questions/10519873/how-to-create-a-line-plot-with-groups-in-base-r-without-loops)
old StackOverflow thread for a longer discussion.

[^2]: See the accompanying help pages of those two functions for more
2. See the accompanying help pages of those two functions for more
details on the available palettes, or read the
[article](https://arxiv.org/pdf/2303.04918.pdf) by Achim Zeileis and
Paul Murrell.
71 changes: 71 additions & 0 deletions inst/tinytest/_tinysnapshot/pointrange_triangle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 10 additions & 8 deletions inst/tinytest/test-README.R
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,17 @@ f = function() {
}
expect_snapshot_plot(f, label = "readme_legend_bottom")

f = function() {
plot2(
density(airquality$Temp),
by = airquality$Month,
legend.position = "topright",
legend.args = list(title = "Month", bty="o")
)
if ((getRversion() <= "4.3.1")) {
f = function() {
plot2(
density(airquality$Temp),
by = airquality$Month,
legend.position = "topright",
legend.args = list(title = "Month", bty="o")
)
}
expect_snapshot_plot(f, label = "readme_density_topright")
}
expect_snapshot_plot(f, label = "readme_density_topright")

f = function() {
plot2(
Expand Down
Loading