Skip to content

Commit f75596b

Browse files
tinyplot method for 'ts' time series objects (#558)
* add tinyplot() method for 'ts' time series objects * add documentation * import time() extractor * tests for tinyplot.ts * NEWS item for tinyplot.ts * snapshot updates * NOT_CRAN env var for tinytest CI * histogram and other distribution type exceptions * direct labs example * news * news tweak * gallery update * test website * ci: try to upload review artifact if failure * ci: use glob for review dir * drop false positive and add direct legend version * allow xlab override and tweak examples * update website * add returns field to avoid R CMD check --as-cran NOTE * typos * news tweak * "area" example --------- Co-authored-by: Grant McDermott <gmcd@amazon.com>
1 parent e4e9052 commit f75596b

19 files changed

Lines changed: 1748 additions & 55 deletions

.github/workflows/R-CMD-check.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,14 @@ jobs:
4242

4343
- name: Test
4444
run: ./run.sh run_tests
45+
46+
- name: Upload snapshot review (on failure)
47+
if: failure()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: tinysnapshot-review-${{ matrix.name }}
51+
path: |
52+
*.Rcheck/**/_tinysnapshot_review/**
53+
*.Rcheck/00check.log
54+
if-no-files-found: ignore
55+
retention-days: 7

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ S3method(tinyplot,data.frame)
44
S3method(tinyplot,default)
55
S3method(tinyplot,density)
66
S3method(tinyplot,formula)
7+
S3method(tinyplot,ts)
78
export(draw_legend)
89
export(get_saved_par)
910
export(plt)
@@ -135,6 +136,7 @@ importFrom(stats,reformulate)
135136
importFrom(stats,setNames)
136137
importFrom(stats,spline)
137138
importFrom(stats,terms)
139+
importFrom(stats,time)
138140
importFrom(stats,weighted.mean)
139141
importFrom(tools,file_ext)
140142
importFrom(utils,globalVariables)

NEWS.md

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ Theme fixes:
148148
subtitle, and legend stack correctly above the plot region with proper
149149
spacing. (#605)
150150

151-
### New plot types
151+
### New features
152+
153+
#### New plot types
152154

153155
- `type_chull()` (equivalently, `type = "chull"`) for drawing convex hulls
154156
around grouped points. Uses `grDevices::chull()` under the hood and delegates
@@ -158,10 +160,10 @@ Theme fixes:
158160
ellipses around grouped points. Like `type_chull`, works well as a filled
159161
layer, e.g. `plt_add(type = "ellipse", fill = 0.2)`. (#610 @grantmcdermott)
160162

161-
### Other new features
163+
#### New `tinyplot.*` methods
162164

163-
- A new top-level `tinypairs()` function, together with a dedicated
164-
`tinyplot.data.frame()` method now supports direct plotting of data frames,
165+
- A top-level `tinypairs()` function, together with a dedicated
166+
`tinyplot.data.frame()` method, now supports direct plotting of data frames,
165167
with or without a formula. Combining with a formula is mostly useful insofar
166168
as it facilitates piping, e.g.
167169

@@ -174,7 +176,20 @@ Theme fixes:
174176
variables will yield a `pairs()`-style grid of all variable combinations.
175177
Thanks to @mthulin for the suggestion and original implementation idea.
176178
(#613, #640 @zeileis @grantmcdermott)
177-
- New top-level `tinyplot()`/`plt()` arguments:
179+
- New dedicated `tinyplot.ts()` method for `ts` time series, e.g.
180+
181+
```r
182+
plt(EuStockMarkets)
183+
```
184+
185+
Produces a line plot by default, although users can override by passing an
186+
explicit `type` argument. Similarly, multivariate series are faceted by
187+
default, but users can also override to obtain, say, a single frame with
188+
direct labels. (#558 @zeileis @grantmcdermott)
189+
190+
#### Other new features
191+
192+
- New and updated top-level `tinyplot()`/`plt()` arguments:
178193
- `cap = <string>` for adding a caption to your plots. Captions are drawn at
179194
the bottom of the plot and are best paired with dynamic themes (since
180195
separation from `sub` is guaranteed). Appearance is customizable via
@@ -211,33 +226,37 @@ Theme fixes:
211226
```
212227
The `labels` arg is silently ignored for non-text types.
213228
(#639 @grantmcdermott)
214-
- The `grid` argument (and `tpar("grid")`) now accepts character strings to
215-
control axis-specific grids at different resolutions. Uppercase letters
216-
(`"X"`, `"Y"`, `"XY"`) draw grid lines at the standard tick positions, while
217-
lowercase letters (`"x"`, `"y"`, `"xy"`) draw a finer grid with additional
218-
lines at the midpoints between ticks. Thanks to @zeileis for the suggestion.
219-
(#578 @grantmcdermott)
220-
- Facet formulas now support `1` as a convenience syntax for single row or
221-
column arrangements. (#562 @zeileis)
222-
- `plt(..., facet = z ~ 1)` <-> `plt(..., facet = ~z, facet.args = list(ncol = 1))`
223-
- `plt(..., facet = 1 ~ z)` <-> `plt(..., facet = ~z, facet.args = list(nrow = 1))`.
224-
- `type_barplot()` gains an `offset` argument for shifting bar baselines away
225-
from zero. (#611, #615 @grantmcdermott @zeileis)
226-
- If the offset is an unnamed scalar or numeric vector, it shifts the bars
227-
positionally by the given values. Useful for creating waterfall plots and
228-
floating bars.
229-
- If the offset is a character or named numeric vector, it instead "sets
230-
aside" the named level(s) of the `by` group, pulling them out of the stack
231-
and drawing them as standalone bars. This is useful for Likert plots, where
232-
you want to show a neutral categories (e.g., "Unsure") apart from the
233-
diverging stack. Thanks to @strengejacke for the suggestion.
234-
- `type_text()` gains two new arguments:
235-
- a `labeller` argument that is passed to `tinylabel()` for formatting the
236-
text labels. (#620 @grantmcdermott)
237-
- a `repel` argument that automatically nudges overlapping text labels apart.
238-
One limitation is that the repulsion logic operates with groups. So there
239-
may still be some overlapping text for for grouped data.
240-
(#621 @grantmcdermott)
229+
- The `grid` argument (and `tpar("grid")`) now accepts character strings to
230+
control axis-specific grids at different resolutions. Uppercase letters
231+
(`"X"`, `"Y"`, `"XY"`) draw grid lines at the standard tick positions, while
232+
lowercase letters (`"x"`, `"y"`, `"xy"`) draw a finer grid with additional
233+
lines at the midpoints between ticks. Thanks to @zeileis for the suggestion.
234+
(#578 @grantmcdermott)
235+
- Facet formulas now support `1` as a convenience syntax for single row or
236+
column arrangements. (#562 @zeileis)
237+
- `plt(..., facet = z ~ 1)` <-> `plt(..., facet = ~z, facet.args = list(ncol = 1))`
238+
- `plt(..., facet = 1 ~ z)` <-> `plt(..., facet = ~z, facet.args = list(nrow = 1))`.
239+
- Type-specific updates:
240+
- `type_barplot()` gains an `offset` argument for shifting bar baselines away
241+
from zero. (#611, #615 @grantmcdermott @zeileis)
242+
- If the offset is an unnamed scalar or numeric vector, it shifts the bars
243+
positionally by the given values. Useful for creating waterfall plots and
244+
floating bars.
245+
- If the offset is a character or named numeric vector, it instead "sets
246+
aside" the named level(s) of the `by` group, pulling them out of the stack
247+
and drawing them as standalone bars. This is useful for Likert plots,
248+
where you want to show a neutral categories (e.g., "Unsure") apart from
249+
the diverging stack. Thanks to @strengejacke for the suggestion.
250+
- `type_text()` gains two new arguments:
251+
- a `labeller` argument that is passed to `tinylabel()` for formatting the
252+
text labels. (#620 @grantmcdermott)
253+
- a `repel` argument that automatically nudges overlapping text labels
254+
apart. One limitation is that the repulsion logic operates with groups. So
255+
there may still be some overlapping text for for grouped data.
256+
(#621 @grantmcdermott)
257+
- Model-fit and various distribution types gain a `weights` argument; although
258+
this is best provided from the top-level `tinyplot()`/`plt()` call. See
259+
above.
241260

242261
### Bug fixes
243262

R/tinyplot.data.frame.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#' to disambiguate from `frame.plot`.
3838
#' @param ... further arguments passed to `tinyplot`.
3939
#'
40+
#' @returns No return value, called for the side effect of producing a plot.
41+
#'
4042
#' @examples
4143
#' ## using tinyplot() with data frames
4244
#' tinyplot(cars)

R/tinyplot.ts.R

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#' tinyplot Method for Plotting ts Objects (Time Series)
2+
#'
3+
#' @description Convenience interface for visualizing \code{\link[stats]{ts}}
4+
#' (time series) objects with tinyplot.
5+
#'
6+
#' @details Internally the time series object is converted to a long
7+
#' data frame with columns `Time` (time index), `Value` (observations),
8+
#' and `Series` (factor with column labels). Depending on the settings
9+
#' of `facet` this data frame is visualized either with the formula
10+
#' `Value ~ Time` or `Value ~ Time | Series`. See the `facet` argument
11+
#' description for more details and the Examples below for some illustrations.
12+
#'
13+
#' An exception is made if the user explicitly supplies a distribution `type`
14+
#' argument (i.e, one of `"histogram"`, `"density"`, `"boxplot"`, `"violin"`,
15+
#' `"qq"`, `"ridge"`, or `"rug"`). These summarize the series values and
16+
#' ignore the time index, so they are visualized with the one-sided
17+
#' formula `~ Value` (or `~ Value | Series` for multivariate series). This
18+
#' preserves base-R-compatible behaviour such as
19+
#' `tinyplot(Nile, type = "histogram")`.
20+
#'
21+
#' @param x an object of class `"ts"`.
22+
#' @param facet specification of `facet` for `tinyplot.formula`. The
23+
#' default in the `tinyplot` method is to use `facet = NULL` for univariate
24+
#' series and `facet = ~ Series` (equivalent to `facet = "by"`) for
25+
#' multivariate series.
26+
#' @param type,facet.args,xlab,ylab,... further arguments passed to `tinyplot`.
27+
#'
28+
#' @returns No return value, called for the side effect of producing a plot.
29+
#'
30+
#' @examples
31+
#' ## univariate series
32+
#' tinyplot(Nile)
33+
#'
34+
#' # exception: expicitly passing a distribution type on a univariate series
35+
#' # still triggers the corresponding transformation
36+
#' tinyplot(Nile, type = "histogram")
37+
#'
38+
#' ## multivariate series (generally, these also look better with a theme)
39+
#' tinytheme("clean2")
40+
#' tinyplot(EuStockMarkets) ## faceted, free scales, same color
41+
#' tinyplot(EuStockMarkets, facet.args = NULL) ## faceted, same scale, same color
42+
#' tinyplot(EuStockMarkets, facet = "by") ## faceted, free scales, diff colors
43+
#' tinyplot(EuStockMarkets, facet = NULL) ## single frame, diff colors
44+
#'
45+
#' ## further variations
46+
#' tinyplot(EuStockMarkets, facet = NULL, legend = list("direct", repel = TRUE))
47+
#' tinyplot(EuStockMarkets, facet = "by", facet.args = NULL)
48+
#' tinyplot(EuStockMarkets, facet.args = list(free = TRUE, ncol = 1))
49+
#'
50+
#' ## pass additional tinyplot args through `...` for further customization
51+
#' tinyplot(EuStockMarkets,
52+
#' facet.args = NULL,
53+
#' type = "area", xlab = NA, yaxl = ",",
54+
#' main = "European stock indices")
55+
#'
56+
#' tinytheme() ## reset
57+
#'
58+
#' @importFrom stats time
59+
#' @export
60+
tinyplot.ts = function(x, facet, type = "l", facet.args = list(free = TRUE), xlab = NULL, ylab = NA, ...) {
61+
## basic object properties
62+
n = NROW(x)
63+
k = NCOL(x)
64+
lab = deparse(substitute(x))
65+
if (k > 1L) lab = paste(lab, 1L:k, sep = ".")
66+
if (!is.null(colnames(x))) lab = colnames(x)
67+
68+
single = k == 1L
69+
70+
## convert to long data.frame
71+
df = data.frame(
72+
Time = rep.int(as.numeric(time(x)), k),
73+
Value = as.numeric(x),
74+
Series = factor(rep(1L:k, each = n), labels = lab)
75+
)
76+
77+
## Distribution types summarize the series *values* and ignore the time index,
78+
## so they dispatch with a one-sided formula (`~ Value`, or `~ Value | Series`
79+
## when multivariate) rather than `Value ~ Time`, and skip the time-series
80+
## `ylab` default so tinyplot's own label (e.g. "Frequency") shows. This
81+
## preserves base-R-compatible behaviour like `tinyplot(Nile, type = "histogram")`.
82+
dist_types = c("histogram", "hist", "density", "boxplot", "box",
83+
"violin", "qq", "ridge", "rug")
84+
## normalize to a type name to catch both the string ("histogram") and the
85+
## type object (`type_histogram()`, whose `$name` is the canonical "histogram")
86+
type_name = if (inherits(type, "tinyplot_type")) {
87+
type[["name"]]
88+
} else if (is.character(type) && length(type) == 1L) {
89+
type
90+
} else {
91+
NA_character_
92+
}
93+
is_dist = !is.na(type_name) && type_name %in% dist_types
94+
95+
## default for facet
96+
if(missing(facet)) {
97+
auto = TRUE
98+
facet = if(single || is_dist) NULL else ~ Series
99+
} else {
100+
auto = FALSE
101+
}
102+
if (is.null(facet)) facet.args = NULL
103+
104+
## dispatch formula and axis labels
105+
if (is_dist) {
106+
fml = if (single) ~ Value else ~ Value | Series
107+
if (single && is.null(xlab)) xlab = lab
108+
## use tinyplot's own ylab default (e.g. "Frequency") unless caller set one
109+
if (missing(ylab)) ylab = NULL
110+
} else if (single || (!is.null(facet) && auto)) {
111+
fml = Value ~ Time
112+
} else {
113+
fml = Value ~ Time | Series
114+
}
115+
116+
## call tinyplot
117+
tinyplot(fml, data = df, type = type, facet = facet, facet.args = facet.args,
118+
xlab = xlab, ylab = ylab, ...)
119+
}

altdoc/pkgdown.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ altdoc: 0.7.2
22
pandoc: '3.10'
33
pkgdown: 2.1.3
44
pkgdown_sha: ~
5-
last_built: 2026-06-22T18:44:13+0000
5+
last_built: 2026-06-23T18:52:00+0000
66
urls:
77
reference: https://grantmcdermott.com/tinyplot/man
88
article: https://grantmcdermott.com/tinyplot/vignettes

altdoc/quarto_website.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,6 @@ website:
6262
file: man/tinyplot.qmd
6363
- text: tinyplot_add
6464
file: man/tinyplot_add.qmd
65-
- text: tinypairs
66-
file: man/tinyplot.data.frame.qmd
67-
# - section: "Methods"
68-
# contents:
69-
# - text: tinyplot.data.frame
70-
# file: man/tinyplot.data.frame.qmd
7165
- section: "Types"
7266
contents:
7367
- section: Shapes
@@ -166,6 +160,8 @@ website:
166160
contents:
167161
- text: tinyplot.data.frame
168162
file: man/tinyplot.data.frame.qmd
163+
- text: tinyplot.ts
164+
file: man/tinyplot.ts.qmd
169165

170166
format:
171167
html:

0 commit comments

Comments
 (0)