Skip to contents

stat_peaks finds local y maxima and stat_valleys finds local y minima. Both stats identify in data or extract from data rows matching peaks or valleys adding formatted character labels to the returned data frame. The formatting is controlled by a format string compatible with sprintf() or strftime(). When all rows in data are returned, labels are set to "" for observations that are not peaks or valleys.

Usage

stat_peaks(
  mapping = NULL,
  data = NULL,
  geom = "point",
  position = "identity",
  ...,
  span = 5,
  global.threshold = 0,
  local.threshold = 0,
  local.reference = "minimum",
  threshold.scaling = "data.range",
  strict = FALSE,
  label.fmt = NULL,
  x.label.fmt = NULL,
  y.label.fmt = NULL,
  extract.peaks = NULL,
  orientation = "x",
  na.rm = FALSE,
  show.legend = FALSE,
  inherit.aes = TRUE
)

stat_valleys(
  mapping = NULL,
  data = NULL,
  geom = "point",
  position = "identity",
  ...,
  span = 5,
  global.threshold = 0,
  local.threshold = 0,
  local.reference = "maximum",
  threshold.scaling = "data.range",
  strict = FALSE,
  label.fmt = NULL,
  x.label.fmt = NULL,
  y.label.fmt = NULL,
  extract.valleys = NULL,
  orientation = "x",
  na.rm = FALSE,
  show.legend = FALSE,
  inherit.aes = TRUE
)

Arguments

mapping

The aesthetic mapping, usually constructed with aes. Only needs to be set at the layer level if you are overriding the plot defaults.

data

A layer specific dataset - only needed if you want to override the plot defaults.

geom

The geometric object to use display the data.

position

The position adjustment to use for overlapping points on this layer.

...

other arguments passed on to layer. This can include aesthetics whose values you want to set, not map. See layer for more details.

span

odd integer A peak is defined as an element in a sequence which is greater than all other elements within a moving window of width span centred at that element. The default value is 5, meaning that a peak is taller than its four nearest neighbours. span = NULL extends the span to the whole length of x.

global.threshold

numeric A value between 0.0 and 1.0, relative to threshold.range indicating the global height (depth) threshold below which peaks (valleys) will be ignored, or a negative value, between 0.0 and -1.0 indicating the global height (depth) threshold above which peaks (valleys) will be ignored. If threshold.range = 0 or the value passed as argument belongs to class "AsIs" the value is interpreted as an absolute value expressed in data units.

local.threshold

numeric A value between 0.0 and 1.0, relative to threshold.range, indicating the within-window height (depth) threshold below which peaks (valleys) will be ignored. If threshold.range = 0 or the value passed as argument belongs to class "AsIs" the value is interpreted as an absolute value expressed in data units.

local.reference

character One of "minimum" (eqv. "maximum") or "median". The reference used to assess the height of the peak, either the minimum (maximum) value within the window or the median of all values in the window.

threshold.scaling

character One of "data.range", "scale.range", or "none".

strict

logical flag: if TRUE, an element must be strictly greater than all other values in its window to be considered a peak. Default: TRUE.

label.fmt

character string giving a format definition for converting values into character strings by means of function sprintf or strptime, its use is deprecated.

x.label.fmt

character string giving a format definition for converting $x$-values into character strings by means of function sprintf or strftime. The default argument varies depending on the scale in use.

y.label.fmt

character string giving a format definition for converting $y$-values into character strings by means of function sprintf.

extract.peaks, extract.valleys

logical If TRUE only the rows of data matching peaks or valleys are returned, if FALSE all rows are returned.

orientation

character Either "x" or "y".

na.rm

a logical value indicating whether NA values should be stripped before the computation proceeds.

show.legend

logical. Should this layer be included in the legends? NA, the default, includes if any aesthetics are mapped. FALSE never includes, and TRUE always includes.

inherit.aes

If FALSE, overrides the default aesthetics, rather than combining with them. This is most useful for helper functions that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. borders.

Details

These stats use geom_point by default as it is the geom most likely to work well in almost any situation without need of tweaking. The default aesthetics set by these stats allow their direct use with geom_text, geom_label, geom_line, geom_rug, geom_hline and geom_vline. The formatting of the labels returned can be controlled by the user.

The default for parameter strict is TRUE in functions splus2R::peaks() and find_peaks(), while the default is FALSE in stat_peaks() and in stat_valleys().

A problem faced when identifying peaks is their relevance. One approach is to ignore peaks based on their height, which can be done either globally for the whole variable mapped to the y aesthetic or within a narrower window. These two approaches can be combined. The threshold height (depth) can be given in data units by protecting the argument in a call to I() or by passing threshold.scaling = "none". They can be also expressed relative to the range of y by passing threshold.scaling = "data.range", or relative to the range of the scale used for y by passing threshold.scaling = "scale.range". The default threshold.scaling = "data.range" is the same as the fixed value in versions <= 0.6.1 of 'ggpmisc'.

While when highlighting or labelling peaks and/or valleys with other geoms the best approach is to extract the observations, when using repulsive geoms from 'ggrepel' to avoid overlaps it is necessary to retain all observations and to set the label to "" for observations not to be labelled. By default the switch between these two approaches is automatic, based on the argument passed to geom. However, either behaviour can be forced by passing an argument to extract.peaks or extract.valleys.

Note

These statistics check the scale of the x aesthetic and if it is Date or Datetime they correctly generate the labels by transforming the numeric x values to Date or POSIXct objects, respectively. In which case the x.label.fmt must follow the syntax supported by strftime() rather than by sprintf(). Overlap of labels with points can be avoided by use of one of the nudge positions, possibly together with geometry geom_text_s from package ggpp, or with geom_text_repel or geom_label_repel from package ggrepel. To discard overlapping labels use check_overlap = TRUE as argument to geom_text or geom_text_s. By default the labels are character values suitable to be plotted as is, but with a suitable format passed as argument to label.fmt labels suitable for parsing by the geoms (e.g., into expressions containing Greek letters, super- or subscripts, maths symbols or maths constructs) can be also easily obtained.

Returned and computed variables

x

x-value at the peak (or valley) as numeric

y

y-value at the peak (or valley) as numeric

is.peak/is.valley

logical value

x.label

x-value at the peak (or valley) as character

y.label

y-value at the peak (or valley) as character

Warning!

The current version of these statistics does not support passing nudge_x or nurge_y named parameters to the geometry. Instead, use as an argument to parameter `position` one of the position functions such as position_nudge_keep.

See also

find_peaks for details on how peaks and valleys are found.

Examples

# lynx is a time.series object
# we convert it to a data frame
lynx_num.df <-
  try_tibble(lynx,
             col.names = c("year", "lynx"),
             as.numeric = TRUE) # years -> as numeric

# using defaults
ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red") +
  stat_valleys(colour = "blue")


# global threshold for peak height
ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
             global.threshold = 0.5,
             threshold.scaling = "data.range") # half data range


ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
             global.threshold = 0.5,
             threshold.scaling = "scale.range") + # half scale range
             expand_limits(y = c(0, 8000))


ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
  global.threshold = I(4000))


# local (within window) threshold for peak height
ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
             local.threshold = 1/3,
             local.reference = "minimum")


ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
             local.threshold = 1/5,
             local.reference = "median")


ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
  global.threshold = I(3000))


# orientation is supported
ggplot(lynx_num.df, aes(lynx, year)) +
  geom_line(orientation = "y") +
  stat_peaks(colour = "red", orientation = "y") +
  stat_valleys(colour = "blue", orientation = "y")


# default aesthetic mapping supports additional geoms
ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red") +
  stat_peaks(colour = "red", geom = "rug")


ggplot(lynx_num.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red") +
  stat_peaks(colour = "red", geom = "text", hjust = -0.1, angle = 33)


ggplot(lynx_num.df, aes(lynx, year)) +
  geom_line(orientation = "y") +
  stat_peaks(colour = "red", orientation = "y") +
  stat_peaks(colour = "red", orientation = "y",
             geom = "text", hjust = -0.1)


# date times and dates are also supported for x aesthetic
lynx_datetime.df <-
   try_tibble(lynx,
              col.names = c("year", "lynx")) # years -> POSIXct

ggplot(lynx_datetime.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red") +
  stat_valleys(colour = "blue")


ggplot(lynx_datetime.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red") +
  stat_peaks(colour = "red",
             geom = "text",
             hjust = -0.1,
             x.label.fmt = "%Y",
             angle = 33)


ggplot(lynx_datetime.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red") +
  stat_peaks(colour = "red",
             geom = "text_s",
             position = position_nudge_keep(x = 0, y = 200),
             hjust = -0.1,
             x.label.fmt = "%Y",
             angle = 90) +
  expand_limits(y = 8000)


ggplot(lynx_datetime.df, aes(year, lynx)) +
  geom_line() +
  stat_peaks(colour = "red",
             geom = "text_s",
             position = position_nudge_to(y = 7600),
             arrow = arrow(length = grid::unit(1.5, "mm")),
             point.padding = 0.7,
             x.label.fmt = "%Y",
             angle = 90) +
  expand_limits(y = 9000)