Skip to contents
library(roboplotr)
knitr::opts_chunk$set(message = FALSE, warning = FALSE)

Basics

Plotly is especially challenging to use when the resulting plots are dynamically sized, as elements external to the plot area, like titles, captions, legends and logos will not be located where they are expected to be after a resize. The purpose of the roboplotr package is to reduce the workload of making dynamic plotly plots, with many layout elements handled with parameters to the core roboplotr function, roboplot(). Some more complicated things are handled with helper functions. Roboplotr has internal scripts to handle plot relayouts on a resize, including dynamic title wraps and y-axis font resizing.

This vignette will show an example how to use roboplotr package to create an html report with several interactive plots in one go with minimum hassle for the plots themselves, and we will also capture all the created plots as separate files in png format. For the purposes of this report we will use set widths for all the plots, but roboplot truly shines when you need your plots to fit different device widths, in which case you would not give set widths to the plots.

Every function parameter we use here (and the ones we don’t) have an example of use in the function documentation.

Setting the global options

First off, we use set_roboplot_options() to set some global options to define some visual and other specifications. We create the directory “plots” in tempdir() to store any files created in.


dir.create(file.path(tempdir(), "plots"))

set_roboplot_options(
  artefacts = set_artefacts(
    filepath = file.path(tempdir(), "plots"),
    auto = T,
    artefacts = "png"
    ),
  caption_template = "Source: {text}.",
  height = 400,
  imgdl_wide = set_imgdl_layout(
    width = 650,
    height = 400,
    suffix = "",
    format = "png"
  ),
  imgdl_narrow = set_imgdl_layout(
    width = 550,
    height = 440,
    suffix = "",
    format = "png"
  ),
  modebar = c(
    "home",
    "closest",
    "compare",
    "zoom",
    "img_n",
    "img_w",
    "data_dl"
  ),
  logo_file = system.file("images", "robonomist.png", package = "roboplotr"),
  trace_colors = grDevices::palette.colors(6, "Set2"),
  width = 650,
  xaxis_ceiling = "guess",
  verbose = "Warning"
)

Above we defined the logo used in the plots, defined how captions are constructed (avoiding repetedly writing the same information). Modebar elements were also defined, with further specifications for image downloads for the external png files. We also defined the colors used by all plot traces in the order we want them to be used, as well as the plot dimensions, and the x-axis ceiling handling.

When xaxis_ceiling is provided, any scatter plots with date format are given some breathing room to the upper end of the range by rounding the last date unit to the next unit of the denomination given, ie. if the last date value is "2022-10-07", the x-axis upper bound will be "2022-11-01".

Captions, height, width, xaxis_ceiling, trace colors and artefacts can be controlled by-plot, too, but giving them here avoids writing repeatedly. As roboplot() is written with dynamic plot widhts in mind, it’s usually unnecessary to define it, but for demonstration purposes we provided a width, as we want our rendered plots to be the same width as the corresponding png files we are going to create.

The specifications for image downloads are defined with imgdl_wide and imgdl_narrow. These parameters define the desired image dimensions for static image download buttons in the modebar, and provide a suffix for the created files, and define the file format. We set these to match our intended programmatically created .png files.

We also want to automate the artefact file creation. This can be controlled by-plot with the parameter artefacts of roboplot(), but we’ll use set_artefacts() here to set some global options. We set auto to true (to make roboplot() create the files automatically when creating a plot), and give filepath where the files will be located. As all our plots have height and width set, these will be used in the resulting static plots, too, unless overridden by artefacts.

We also set verbose to “Warning” that quiets most of the messages roboplotr gives.

Next we call some libraries we need for the data we are going to plot, and create our first plot.

Basic use

library(tidyverse)
library(robonomistClient)
library(lubridate)

d <- data_get("oecd/EO") |>
  filter(
    Country %in% c("Finland", "Euro area (17 countries)", "United States", 
                   "United Kingdom", "Japan"),
    Frequency == "Quarterly",
    Variable == "Gross domestic product, volume, market prices",
    time >= "2017-01-01"
  ) |>
  collect() |>
  with_groups(
    Country, mutate,
    value = 100 * value / mean(value[year(time) == 2019])
  ) |>
  mutate(Country = recode(Country, "Euro area (17 countries)" = "Euro area"))

attr(d, "frequency") <- "Quarterly"

roboplot(d,
    Country,
    title = "Real GDP forecasts",
    subtitle = "Index, 2019=100",
    caption = "OECD"
  )

At the minimum, roboplot() needs some data to be plotted, a title, and a caption. Subtitle is optional, but most likely needed - it is the best place to show the y-axis unit. Caption is parsed from the given string and elements defined in set_roboplot_options().

Data transformation also stripped the data of the frequency attribute, which roboplot() uses to parse date formats for several purposes, so we inserted that back. We could have skipped this, too, leaving it up to roboplot() to guess the frequency.

Caption control

Next we will create another plot, with a caption that does not respect the globally set format.

inflation_eu <-
  data_get("eurostat/prc_hicp_manr") |>
  filter(
    unit == "Annual rate of change",
    coicop == "All-items HICP",
    geo %in% c("Germany (until 1990 former territory of the FRG)",
      "Euro area - 19 countries  (from 2015)","Finland","Sweden"),
    time >= "2015-01-01"
  ) |>
  mutate(
    geo = recode(geo,
      "Euro area - 19 countries  (from 2015)" = "Euro area",
      "Germany (until 1990 former territory of the FRG)" = "Germany"
    )
  ) |>
  select(geo, time, value)

inflation_us <-
  data_get("fred/CPIAUCSL", units = "pc1") |>
  drop_na() |>
  filter(time >= "2015-01-01") |>
  transmute(geo = "USA", time, value)

d <- bind_rows(inflation_us, inflation_eu)

d |> roboplot(geo, "Consumer price inflation","Annual change of HICP, %",
                  caption = set_caption(template = "Sources: Eurostat & US BLS."))

The previous examples show how plotting with roboplotr is a lightweight task. As the source notation is a bit different from the previous plot and we have predefined caption formatting previously with set_roboplot_options(), we must use set_caption() to override the prefix instead of just giving a single string.

Horizontal bar plots and x-axis string width, and omitted legends

Next we make a horizontal plot on producer prices, where we need to be be a bit more verbose with plot axes.

d <-
  data_get(
    "StatFin/thi/statfin_thi_pxt_118g.px", lang = "en", tidy_time = TRUE) |>
  filter(str_detect(
    `Products by activity (CPA 2015, MIG)`,
    "^(01|02|03|07|08|10|16|17|19|20|21|22|23|24|25|26|27|28|29|30|35|36|42) "),
    `Index series` %in% c("Producer price index for manufactured products"),
    Information %in% c("Annual change, %")
  ) |>
  filter(time == max(time)) |>
  rename(CPA = `Products by activity (CPA 2015, MIG)`) |>
  drop_na(value) |>
  mutate(CPA = fct_reorder(str_replace(CPA, "[0-9]{2} ", ""), value))

d_title <- str_c("Producer prices in Finland ",format(unique(d$time), "%m/%Y"))

d |> roboplot(
  title = d_title, 
  subtitle = "%, annual change", 
  caption = "Statistics Finland",
  plot_type = "bar",
  plot_mode = "horizontal",
  plot_axes = set_axes(y = "CPA"),
  legend_maxwidth = 45
  )

As we wanted to present a horizontal bar plot, we used the parameters plot_type and plot_mode from roboplot() to define the plot as such.

Before plotting, we reordered the column that would be y-axis as factor based on value, as roboplot() only does this independently for the column that defines color.

We also used plot_axes parameter of roboplot() and the function set_axes() parameter y to set the column “CPA” from the data to be the yaxis of the plot. set_axes() can take further parameters (described in detail in function documentation), but for a horizontal bar plot where you have a numeric column named "value", set_axes() can handle the ticktypes etc. for the plot by just providing y-axis data column name as parameter y. For most cases this is enough.

As we didn’t want to color-code the bars, we skipped providing color for roboplot(). As there is now only a single trace color, roboplot() omitted the legend. We could also have provided another column for color that only has a single observation, and the result would have been the same. We could have also included the legend with setting legend_position to "bottom" (currently the only option), or forced legend to be omitted by using legend_position = NA.

As many of the tick labels would have been very long and would have made the plot area are very narrow, we also provided legend_maxwidth. This would normally cut legend items shorter, but with character strings in y-axis, it shortens those too with an ellipsis to a maximum character length we provided. Full texts can still be seen on hover.

Next we will create another plot with only a single observation and different dimensions, this time a line plot.

data_get("tidy/thi") |>
  filter(
    Indeksisarja == "Teollisuuden tuottajahintaindeksi",
    Tiedot == "Pisteluku (2015=100)",
    Toimiala == "Yhteensä"
  ) |>
  mutate(value = 100 * (value / lag(value, 12) - 1),
         Indeksisarja = "Producer Price Index for Manufactured Products") |>
  drop_na() |>
  roboplot(Indeksisarja,
    "Producer Price Index for Manufactured Products",
    "Annual change, %",
    "Statistics Finland",
    height = 440,
    width = 550
  )

We needed to drop values of NA that appeared in data transformation, as otherwise the plot x-axis would have shown an unnecessary gap on the left side of the plot. roboplot() will omit rows from data where all values are NA, but keeps them otherwise.

We decided to provide a column name for the parameter color, but as the plot takes the hovertemplate text from data, we had to translate the column text manually into English to match the plot. We could have just not have provided color just as well as there is only a single trace.

We also wanted a plot with different dimensions, and we give the the same dimensions that we decided the narrow png images will have, so the output and png files match.

As you can see from the next few examples, all of this makes the workflow for createing dynamic plots very short and simple.

data_get("tidy/fao_food_price_index") |>
  filter(Type == "Nominal",Variable == "Food Price Index") |>
  roboplot(
    Variable,"FAO Monthly Real Food Price Index","Index, 2014–2016=100","FAO"
    )

Controlling x-axis format

The next two plots are weekly and daily data, and roboplot() tries to guess the correct date format from data frequency and zoom level. Sometimes you have a specific format in mind, and we’ll cover these next.

data_get("tidy/ec_oil_bulletin") |>
  filter(
    Country %in% c("FI","SE", "DE"), 
    Variable == "Euro-super 95  (I), 1000L", 
    Taxes == T, 
    time >= "2018-01-01") |>
  mutate(
    value = value / 1000,
    Country = recode(Country, FI = "Finland", DE = "Germany", SE = "Sweden")) |>
  roboplot(
    Country,
    "Petrol price, 95E10",
    "Euros per litre",
    "European Commission",
    height = 440,
    width = 550,
    plot_axes = set_axes(xformat = "%Y")
  )
d <-
  str_c("entsoe/dap_", c("FI","DE_LU","SE3","FR")) |>
  map(data_get, start_time = "2020-01-01") |>
  bind_rows() |>
  arrange(time) |>
  group_by(Area) |>
  mutate(
    ma28 = slider::slide_index_dbl(value, time, mean, .before = days(28))
  ) |>
  group_by(Area, time = floor_date(time, "days") |> as_date()) |>
  summarise(value = mean(ma28), .groups = "drop") |>
  arrange(Area, time)

d |>
  roboplot(Area,"Electricity prices, 28-day moving average","€/MWh","Entso-E")

We wanted to to ensure these x-axes are always displayed in years for the first two plot and month / year for the plot with daily data. Using years only for the last We provided xformat in plot_axes. The formatting used is d3 time format. You can control y-axis with yformat d3 number format, or use the appropriate format if you use something other than date data for x-axis or numeric data for y-axis. Character formatting is not currenlty an option.

Control trace order

roboplot() uses the mean of column value if it exists to factorize the column used for color if it’s not factorized beforehand, and displays the trace and legend in that order. If you want to specify another order you have to transform the data itself as in the examples that follow.

d <- data_get("ec/esi_nace2") |>
  filter(
    Country %in% 
      c("European Union (current composition)", "Finland", "Germany", "Sweden"),
    Indicator == 
      "The Economic sentiment indicator is a composite measure (average = 100)",
    time >= "2017-01-01"
  ) |>
  mutate(
    Country = 
      recode(Country, "European Union (current composition)" = "EU") |>
      fct_relevel("EU","Finland"),
    Indicator = "Economic sentiment indicator"
    )

d |> roboplot(
    Country,
    "Economic sentiment indicator",
    "Score, 100 = long-run average",
    "European Commission"
  )
data_get("ec/esi_nace2") |> 
  filter(Country %in% c("European Union (current composition)", "Finland"),
         Indicator == "Consumer confidence indicator (20%)",
         time >= "1996-01-01"
         ) |> 
  mutate(
    Country = recode(Country, "European Union (current composition)" = "EU") |>
      fct_relevel("EU","Finland")
    ) |>
  roboplot(
    Country, "Consumer confidence indicator","Score","European Commission"
    )

Note that forcats::fct_relevel2() from forcats behaves differently than forcats::fct_relevel().

d <-
  data_get("oecd/KEI", dl_filter = "CSCICP02.FIN+CHN+USA+EA19+JPN") |>
  filter(FREQUENCY == "Monthly")
attr(d, "frequency") <- "Monthly"

d |>
  select(LOCATION, time, value) |>
  filter(time >= "2000-01-01") |>
  mutate(LOCATION = recode(LOCATION,
    "United States" = "USA",
    "China (People's Republic of)" = "China",
    "Euro area (19 countries)" = "Euro area"
  )) |>
  with_groups(LOCATION, mutate,
    value = 10 * scale(value)[, 1] + 100,
    value = 100 * value / mean(value[year(time) == 2019])
  ) |>
  filter(time >= "2018-01-01") |>
  mutate(LOCATION = fct_reorder2(LOCATION, value, time,.desc = F)) |> 
  roboplot(
    LOCATION,"Consumer Confidence Indicator","Index, 2019 = 100","OECD"
    )

Zeroline control

For the next few plots we want to use a stronger zeroline than what the default plotly plots have, so we will use the zeroline parameter. Zeroline can also take a numeric value which locates the zeroline at the given y-axis location.

data_get("tidy/euribor") |>
  filter(str_detect(Korko, "(1|6|12) kk.*360")) |> 
  mutate(Korko = str_remove(Korko,"\\(.*") |> fct_inorder()) |>
  filter(time >= "2014-01-01") |>
  roboplot(Korko,"Short-term interest rates, Euribor","%","Bank of Finland",
    zeroline = TRUE
  )
data_get("tidy/10yield") |>
  drop_na() |>
  filter(time >= "2012-01-01") |>
  select(Maa, time, value) |>
  mutate(Maa = recode(Maa, "Suomi" = "Finland", "Saksa" = "Germany")) |>
  roboplot(
    Maa,
    "Long-term government bond rates",
    "%, 10-year bond",
    caption = set_caption(template = "Sources: Bank of Finland, Deutche Bundesbank & Fed."),
    zeroline = 2)

More axis controls

For the next plot we need to plot data other than dates as x-axis values.

data_get("ecb/YC",
     dl_filter = "B.U2.EUR.4F.G_N_A.SV_C_YM.",
     query_args = list(startPeriod = "2022-01-01"),
     labels = FALSE
     ) |>
  filter(`Financial market data type` |> str_starts("SR_")) |>
  filter(
    time %in% c(max(time), min(time), as.Date(c("2022-02-28","2022-06-01")))
  ) |>
  mutate(
    Year = str_match(`Financial market data type`, "([[:digit:]]*)Y")[,2],
    Month = str_match(`Financial market data type`, "([[:digit:]]*)M")[, 2]
  ) |>
  mutate(across(c(Year, Month), as.integer)) |>
  replace_na(list(Year = 0, Month = 0)) |>
  mutate(Maturity = Year + Month / 12) |>
  select(Maturity, time, value) |>
  mutate(Time = as.character(time)) |>
  arrange(Maturity, value) |>
  roboplot(
    Time,
    "Yield curves for AAA euro area government bonds",
    "% interest rate vs. maturity",
    caption = set_caption(
      template = "The curves show the yield curve at various points in time.<br>Source: ECB."
    ),
    plot_axes = set_axes(x = "Maturity", xticktype = "numeric"),
    height = 440,
    width = 550
  )

We used plot_axes in a similar way we used it with the previous horizontal bar plot, but this time we defined the data column used for x-axis, and provided the type roboplot() will use for tick formatting. Note that to ensure the traces are drawn correctly, you have to order the data yourself before using it it in roboplot().

For this plot we also wanted to show the numeric y-axis with logarithmic scale.

# China housing investment, from National bureau of statistics of China
# Total Value of Exports, Current Period(1,000 US dollars)
# https://data.stats.gov.cn/english/easyquery.htm?cn=A01
d <- tribble(
  ~time, ~value,
  "Oct 2021", 300221418,
  "Nov 2021", 325525287,
  "Dec 2021", 340498780,
  "Jan 2022", 327285953,
  "Feb 2022", 217416504,
  "Mar 2022", 276084613,
  "Apr 2022", 273619664,
  "May 2022", 308244886,
  "Jun 2022", 331264197,
  "Jul 2022", 332964256,
  "Aug 2022", 314920505, 
  "Sep 2022", 322755333,
  "Oct 2022", 298371747
) |> mutate(value = value / 1000000)


d |> 
  roboplot(
    title = "Value of monthly exports from China", 
    subtitle = "Billion US dollars", 
    caption = "National Bureau of Statistics, China", 
    plot_axes = set_axes(xticktype = "character")
    )

Finally, for the last plot we had a time column which was not in date format and we did not want to transform it into such, so we defined the ticktype for x-axis as "character".

Now we only have to check if the png files are in the directory we wanted them to be in.


list.files(str_c(tempdir(),"/plots")) |> 
  str_subset("png$") |> 
  str_c(collapse = "<br>") |>
  # roboplotr:::roboplotr_combine_words() |> 
  htmltools::HTML()
consumer_confidence_indicator.png
consumer_price_inflation.png
economic_sentiment_indicator.png
electricity_prices_28_day_moving_average.png
fao_monthly_real_food_price_index.png
long_term_government_bond_rates.png
petrol_price_95e10.png
producer_price_index_for_manufactured_products.png
producer_prices_in_finland_032024.png
real_gdp_forecasts.png
short_term_interest_rates_euribor.png
value_of_monthly_exports_from_china.png
yield_curves_for_aaa_euro_area_government_bonds.png

Everything seems to be in order! Now you have an html-document with all the plots needed for your report, and .png files of every plot stored, ready to be used in another document. You can do a lot more fine-tuning with roboplotr, and every function of the package has its own documentation that should give you a head start.