library(highcharter)
d1 = read_csv("tech_stocks_csv.zip", show_col_types = FALSE) %>% 
  filter(sale > 0) %>% 
  mutate(conm = stringr::str_to_title(conm), # Converts the string to title case
         datadate = lubridate::ymd(datadate)) # Convert datadate into a date variable

I will also set the Highcharter theme for all the plots

options(highcharter.theme = hc_theme_darkunica())

Q1

Note that while creating d1_1, I arranged it in the end so that we will get ordered bar plot.

d1_1 = d1 %>%
  group_by(conm) %>% 
  summarize(avg_sale = mean(sale), .groups = "drop") %>% 
  arrange(-avg_sale)
hchart(d1_1, "column", hcaes(x = conm, y = round(avg_sale, 2))) %>%
  hc_xAxis(title = list(text = "Company")) %>%
  hc_yAxis(title = list(text = "Average Sale in $ Millions"), 
           labels = list(format = "${value:,.0f}")) %>% # I used ChatGPT to find out how to create this format
  hc_chart(inverted = TRUE)

Q3

Here I will first create a scatterplot. Adding a regression line is a little more complex so I will add that below.

Also, I am rounding the numbers in the calculated ratios.

d1_ratio = d1 %>% 
  mutate(debt_ratio = round((dlc + replace_na(dltt, 0)) / at, 3),
         rnd_int = round(xrd / sale, 3))

d1_ratio %>% 
hchart("scatter", hcaes(x = debt_ratio, y = rnd_int)) %>% 
  hc_xAxis(title = list(text = "Debt Ratio")) %>%
  hc_yAxis(title = list(text = "R&D to Sales Ratio"))

Now let’s add a regression line. There is no direct way to add a regression line easily. We must estimate the regression model first and then use that model to get predicted values. I am using a super useful package in R called broom which is a part of tidyverse to convert the regression model into a data frame of predicted values.

library(broom)
reg = lm(rnd_int ~ debt_ratio, data = d1_ratio)
reg_fit = augment(reg) %>% arrange(debt_ratio)

augment function from broom package adds predicted values from the regression and other statistics back to the original data frame, which is d1_raio in our case. Take a peek at the resulting data frame:

head(reg_fit)

Here, we are intereted in the variable called .fitted which has predicted values of rnd_int based on debt_ratio. I arranged the data frame by debt_ratio so that highcharter can simply superimpose the regression line on our scatterplot.

d1_ratio %>% 
  hchart(type = "scatter", hcaes(x = debt_ratio, y = rnd_int)) %>% 
  hc_add_series(data = reg_fit, type = "line", hcaes(x = debt_ratio, y = .fitted)) %>%
  hc_xAxis(title = list(text = "Debt Ratio")) %>%
  hc_yAxis(title = list(text = "R&D to Sales Ratio"))

Q6

Creating a face plot is also not straightforward. Note that a facetplot makes sense for a static visualization. In internactive visualization, you could place multiple charts side by side on a webpage easily. You may also provide users a dropdown to select the charts they want to see.

Anyway, I will first show you an alterative to the faceplot in Q6 where we are comparing only 2 companies.

Note that I have changed the tooltip quite a bit. Try to understand this code. The last function in the code adds the labels to the bars.


d1 %>% 
  filter(tic %in% c("META", "NVDA")) %>% 
  filter(fyear >= 2010) %>% 
  hchart("column", hcaes(x = as.character(fyear), y = oibdp, group = conm, name = as.character(fyear))) %>%
  hc_colors(c("#5cc9f5", "#b131a2")) %>%
  hc_xAxis(title = list(text = "Fiscal Year")) %>%
  hc_yAxis(title = list(text = "Profits in $ million")) %>%
  hc_legend(align = "center", verticalAlign = "top", layout = "horizontal", symbolRadius = 0) %>%
  hc_tooltip(headerFormat = "",
             pointFormat = 'Year: {point.name}<br><span style="color:{point.color}">\u2714</span> {series.name}: <b>{point.y}</b><br/>') %>%
  hc_plotOptions(series = list(dataLabels = list(enabled = TRUE, format = "{y}")))
NA

Back to creating a facet plot. The solution is to create two separate plots and then arrange them using an obscure fuction called hw_grid() in highcharter. This function is not ported from the JS library. Instead the developer wrote this for R users.

Anyway, the method is to create separate plots for Meta and nvidia and then put them in one column. To save typing, I used map function from purrr package to apply tickers individually to the same plotting function. It creates 2 plots and saves them as a list named charts. Next, I use hw_grid to organize these two vertically. In other words, we can’t create facetplots as easily as we did in ggplot2.


tickers = c("META", "NVDA")
colors = list(META = "#5cc9f5", NVDA = "#b131a2")

charts <- map(tickers, function(x) {
  
  .dt = d1 %>% filter(tic == x)
  
  hchart(.dt, 
         type = "column", 
         hcaes(x = as.character(fyear), y = oibdp, name = fyear))   %>%
    hc_colors(colors[[x]]) %>%
    hc_xAxis(title = list(text = "Fiscal Year")) %>%
    hc_yAxis(title = list(text = "Profits in $ million")) %>%
    hc_title(text = .dt$conm[1]) %>%
    hc_legend(enabled = FALSE) %>%
    hc_tooltip(headerFormat = "",
               pointFormat = 'Year: {point.name}<br><span style="color:{point.color}">\u2714</span> {series.name}: <b>{point.y}</b><br/>') %>%
    hc_plotOptions(series = list(dataLabels = list(enabled = TRUE, format = "{y}")))
})
hw_grid(charts, ncol = 1, rowheight = 600)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBkZl9wcmludDoga2FibGUKICAgIHRoZW1lOiB5ZXRpCiAgICB0b2M6IHllcwotLS0KCmBgYHtyfQpsaWJyYXJ5KGhpZ2hjaGFydGVyKQpgYGAKCgpgYGB7ciBlY2hvPVRSVUV9CmQxID0gcmVhZF9jc3YoInRlY2hfc3RvY2tzX2Nzdi56aXAiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKSAlPiUgCiAgZmlsdGVyKHNhbGUgPiAwKSAlPiUgCiAgbXV0YXRlKGNvbm0gPSBzdHJpbmdyOjpzdHJfdG9fdGl0bGUoY29ubSksICMgQ29udmVydHMgdGhlIHN0cmluZyB0byB0aXRsZSBjYXNlCiAgICAgICAgIGRhdGFkYXRlID0gbHVicmlkYXRlOjp5bWQoZGF0YWRhdGUpKSAjIENvbnZlcnQgZGF0YWRhdGUgaW50byBhIGRhdGUgdmFyaWFibGUKYGBgCgpJIHdpbGwgYWxzbyBzZXQgdGhlIEhpZ2hjaGFydGVyIHRoZW1lIGZvciBhbGwgdGhlIHBsb3RzCgpgYGB7cn0Kb3B0aW9ucyhoaWdoY2hhcnRlci50aGVtZSA9IGhjX3RoZW1lX2Rhcmt1bmljYSgpKQpgYGAKCgojIyBRMSAKCgpOb3RlIHRoYXQgd2hpbGUgY3JlYXRpbmcgYGQxXzFgLCBJIGFycmFuZ2VkIGl0IGluIHRoZSBlbmQgc28gdGhhdCB3ZSB3aWxsIGdldCBvcmRlcmVkIGJhciBwbG90LgoKYGBge3IgZWNobz1UUlVFfQpkMV8xID0gZDEgJT4lCiAgZ3JvdXBfYnkoY29ubSkgJT4lIAogIHN1bW1hcml6ZShhdmdfc2FsZSA9IG1lYW4oc2FsZSksIC5ncm91cHMgPSAiZHJvcCIpICU+JSAKICBhcnJhbmdlKC1hdmdfc2FsZSkKYGBgCgpgYGB7cn0KaGNoYXJ0KGQxXzEsICJjb2x1bW4iLCBoY2Flcyh4ID0gY29ubSwgeSA9IHJvdW5kKGF2Z19zYWxlLCAyKSkpICU+JQogIGhjX3hBeGlzKHRpdGxlID0gbGlzdCh0ZXh0ID0gIkNvbXBhbnkiKSkgJT4lCiAgaGNfeUF4aXModGl0bGUgPSBsaXN0KHRleHQgPSAiQXZlcmFnZSBTYWxlIGluICQgTWlsbGlvbnMiKSwgCiAgICAgICAgICAgbGFiZWxzID0gbGlzdChmb3JtYXQgPSAiJHt2YWx1ZTosLjBmfSIpKSAlPiUgIyBJIHVzZWQgQ2hhdEdQVCB0byBmaW5kIG91dCBob3cgdG8gY3JlYXRlIHRoaXMgZm9ybWF0CiAgaGNfY2hhcnQoaW52ZXJ0ZWQgPSBUUlVFKQpgYGAKCgojIyBRMwoKCkhlcmUgSSB3aWxsIGZpcnN0IGNyZWF0ZSBhIHNjYXR0ZXJwbG90LiBBZGRpbmcgYSByZWdyZXNzaW9uIGxpbmUgaXMgYSBsaXR0bGUgbW9yZSBjb21wbGV4IHNvIEkgd2lsbCBhZGQgdGhhdCBiZWxvdy4KCkFsc28sIEkgYW0gcm91bmRpbmcgdGhlIG51bWJlcnMgaW4gdGhlIGNhbGN1bGF0ZWQgcmF0aW9zLgoKYGBge3J9CmQxX3JhdGlvID0gZDEgJT4lIAogIG11dGF0ZShkZWJ0X3JhdGlvID0gcm91bmQoKGRsYyArIHJlcGxhY2VfbmEoZGx0dCwgMCkpIC8gYXQsIDMpLAogICAgICAgICBybmRfaW50ID0gcm91bmQoeHJkIC8gc2FsZSwgMykpCgpkMV9yYXRpbyAlPiUgCmhjaGFydCgic2NhdHRlciIsIGhjYWVzKHggPSBkZWJ0X3JhdGlvLCB5ID0gcm5kX2ludCkpICU+JSAKICBoY194QXhpcyh0aXRsZSA9IGxpc3QodGV4dCA9ICJEZWJ0IFJhdGlvIikpICU+JQogIGhjX3lBeGlzKHRpdGxlID0gbGlzdCh0ZXh0ID0gIlImRCB0byBTYWxlcyBSYXRpbyIpKQpgYGAKCk5vdyBsZXQncyBhZGQgYSByZWdyZXNzaW9uIGxpbmUuIFRoZXJlIGlzIG5vIGRpcmVjdCB3YXkgdG8gYWRkIGEgcmVncmVzc2lvbiBsaW5lIGVhc2lseS4gV2UgbXVzdCBlc3RpbWF0ZSB0aGUgcmVncmVzc2lvbiBtb2RlbCBmaXJzdCBhbmQgdGhlbiB1c2UgdGhhdCBtb2RlbCB0byBnZXQgcHJlZGljdGVkIHZhbHVlcy4gSSBhbSB1c2luZyBhIHN1cGVyIHVzZWZ1bCBwYWNrYWdlIGluIFIgY2FsbGVkIGBicm9vbWAgd2hpY2ggaXMgYSBwYXJ0IG9mIGB0aWR5dmVyc2VgIHRvIGNvbnZlcnQgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgaW50byBhIGRhdGEgZnJhbWUgb2YgcHJlZGljdGVkIHZhbHVlcy4KCmBgYHtyfQpsaWJyYXJ5KGJyb29tKQpyZWcgPSBsbShybmRfaW50IH4gZGVidF9yYXRpbywgZGF0YSA9IGQxX3JhdGlvKQpyZWdfZml0ID0gYXVnbWVudChyZWcpICU+JSBhcnJhbmdlKGRlYnRfcmF0aW8pCmBgYAoKYGF1Z21lbnRgIGZ1bmN0aW9uIGZyb20gYGJyb29tYCBwYWNrYWdlIGFkZHMgcHJlZGljdGVkIHZhbHVlcyBmcm9tIHRoZSByZWdyZXNzaW9uIGFuZCBvdGhlciBzdGF0aXN0aWNzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGEgZnJhbWUsIHdoaWNoIGlzIGBkMV9yYWlvYCBpbiBvdXIgY2FzZS4gVGFrZSBhIHBlZWsgYXQgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lOgoKYGBge3J9CmhlYWQocmVnX2ZpdCkKYGBgCkhlcmUsIHdlIGFyZSBpbnRlcmV0ZWQgaW4gdGhlIHZhcmlhYmxlIGNhbGxlZCBgLmZpdHRlZGAgd2hpY2ggaGFzIHByZWRpY3RlZCB2YWx1ZXMgb2YgYHJuZF9pbnRgIGJhc2VkIG9uIGBkZWJ0X3JhdGlvYC4gSSBhcnJhbmdlZCB0aGUgZGF0YSBmcmFtZSBieSBgZGVidF9yYXRpb2Agc28gdGhhdCBoaWdoY2hhcnRlciBjYW4gc2ltcGx5IHN1cGVyaW1wb3NlIHRoZSByZWdyZXNzaW9uIGxpbmUgb24gb3VyIHNjYXR0ZXJwbG90LgoKCmBgYHtyfQpkMV9yYXRpbyAlPiUgCiAgaGNoYXJ0KHR5cGUgPSAic2NhdHRlciIsIGhjYWVzKHggPSBkZWJ0X3JhdGlvLCB5ID0gcm5kX2ludCkpICU+JSAKICBoY19hZGRfc2VyaWVzKGRhdGEgPSByZWdfZml0LCB0eXBlID0gImxpbmUiLCBoY2Flcyh4ID0gZGVidF9yYXRpbywgeSA9IC5maXR0ZWQpKSAlPiUKICBoY194QXhpcyh0aXRsZSA9IGxpc3QodGV4dCA9ICJEZWJ0IFJhdGlvIikpICU+JQogIGhjX3lBeGlzKHRpdGxlID0gbGlzdCh0ZXh0ID0gIlImRCB0byBTYWxlcyBSYXRpbyIpKQpgYGAKCiMjIFE2CgpDcmVhdGluZyBhIGZhY2UgcGxvdCBpcyBhbHNvIG5vdCBzdHJhaWdodGZvcndhcmQuIE5vdGUgdGhhdCBhIGZhY2V0cGxvdCBtYWtlcyBzZW5zZSBmb3IgYSBzdGF0aWMgdmlzdWFsaXphdGlvbi4gSW4gaW50ZXJuYWN0aXZlIHZpc3VhbGl6YXRpb24sIHlvdSBjb3VsZCBwbGFjZSBtdWx0aXBsZSBjaGFydHMgc2lkZSBieSBzaWRlIG9uIGEgd2VicGFnZSBlYXNpbHkuIFlvdSBtYXkgYWxzbyBwcm92aWRlIHVzZXJzIGEgZHJvcGRvd24gdG8gc2VsZWN0IHRoZSBjaGFydHMgdGhleSB3YW50IHRvIHNlZS4KCkFueXdheSwgSSB3aWxsIGZpcnN0IHNob3cgeW91IGFuIGFsdGVyYXRpdmUgdG8gdGhlIGZhY2VwbG90IGluIFE2IHdoZXJlIHdlIGFyZSBjb21wYXJpbmcgb25seSAyIGNvbXBhbmllcy4KCk5vdGUgdGhhdCBJIGhhdmUgY2hhbmdlZCB0aGUgdG9vbHRpcCBxdWl0ZSBhIGJpdC4gVHJ5IHRvIHVuZGVyc3RhbmQgdGhpcyBjb2RlLiBUaGUgbGFzdCBmdW5jdGlvbiBpbiB0aGUgY29kZSBhZGRzIHRoZSBsYWJlbHMgdG8gdGhlIGJhcnMuIAoKYGBge3J9CgpkMSAlPiUgCiAgZmlsdGVyKHRpYyAlaW4lIGMoIk1FVEEiLCAiTlZEQSIpKSAlPiUgCiAgZmlsdGVyKGZ5ZWFyID49IDIwMTApICU+JSAKICBoY2hhcnQoImNvbHVtbiIsIGhjYWVzKHggPSBhcy5jaGFyYWN0ZXIoZnllYXIpLCB5ID0gb2liZHAsIGdyb3VwID0gY29ubSwgbmFtZSA9IGFzLmNoYXJhY3RlcihmeWVhcikpKSAlPiUKICBoY19jb2xvcnMoYygiIzVjYzlmNSIsICIjYjEzMWEyIikpICU+JQogIGhjX3hBeGlzKHRpdGxlID0gbGlzdCh0ZXh0ID0gIkZpc2NhbCBZZWFyIikpICU+JQogIGhjX3lBeGlzKHRpdGxlID0gbGlzdCh0ZXh0ID0gIlByb2ZpdHMgaW4gJCBtaWxsaW9uIikpICU+JQogIGhjX2xlZ2VuZChhbGlnbiA9ICJjZW50ZXIiLCB2ZXJ0aWNhbEFsaWduID0gInRvcCIsIGxheW91dCA9ICJob3Jpem9udGFsIiwgc3ltYm9sUmFkaXVzID0gMCkgJT4lCiAgaGNfdG9vbHRpcChoZWFkZXJGb3JtYXQgPSAiIiwKICAgICAgICAgICAgIHBvaW50Rm9ybWF0ID0gJ1llYXI6IHtwb2ludC5uYW1lfTxicj48c3BhbiBzdHlsZT0iY29sb3I6e3BvaW50LmNvbG9yfSI+XHUyNzE0PC9zcGFuPiB7c2VyaWVzLm5hbWV9OiA8Yj57cG9pbnQueX08L2I+PGJyLz4nKSAlPiUKICBoY19wbG90T3B0aW9ucyhzZXJpZXMgPSBsaXN0KGRhdGFMYWJlbHMgPSBsaXN0KGVuYWJsZWQgPSBUUlVFLCBmb3JtYXQgPSAie3l9IikpKQoKYGBgCgoKQmFjayB0byBjcmVhdGluZyBhIGZhY2V0IHBsb3QuIFRoZSBzb2x1dGlvbiBpcyB0byBjcmVhdGUgdHdvIHNlcGFyYXRlIHBsb3RzIGFuZCB0aGVuIGFycmFuZ2UgdGhlbSB1c2luZyBhbiBvYnNjdXJlIGZ1Y3Rpb24gY2FsbGVkIGBod19ncmlkKClgIGluIGhpZ2hjaGFydGVyLiBUaGlzIGZ1bmN0aW9uIGlzIG5vdCBwb3J0ZWQgZnJvbSB0aGUgSlMgbGlicmFyeS4gSW5zdGVhZCB0aGUgZGV2ZWxvcGVyIHdyb3RlIHRoaXMgZm9yIFIgdXNlcnMuCgpBbnl3YXksIHRoZSBtZXRob2QgaXMgdG8gY3JlYXRlIHNlcGFyYXRlIHBsb3RzIGZvciBNZXRhIGFuZCBudmlkaWEgYW5kIHRoZW4gcHV0IHRoZW0gaW4gb25lIGNvbHVtbi4gVG8gc2F2ZSB0eXBpbmcsIEkgdXNlZCBgbWFwYCBmdW5jdGlvbiBmcm9tIGBwdXJycmAgcGFja2FnZSB0byBhcHBseSB0aWNrZXJzIGluZGl2aWR1YWxseSB0byB0aGUgc2FtZSBwbG90dGluZyBmdW5jdGlvbi4gSXQgY3JlYXRlcyAyIHBsb3RzIGFuZCBzYXZlcyB0aGVtIGFzIGEgbGlzdCBuYW1lZCBgY2hhcnRzYC4gTmV4dCwgSSB1c2UgYGh3X2dyaWRgIHRvIG9yZ2FuaXplIHRoZXNlIHR3byB2ZXJ0aWNhbGx5LiBJbiBvdGhlciB3b3Jkcywgd2UgY2FuJ3QgY3JlYXRlIGZhY2V0cGxvdHMgYXMgZWFzaWx5IGFzIHdlIGRpZCBpbiBnZ3Bsb3QyLgoKCmBgYHtyfQoKdGlja2VycyA9IGMoIk1FVEEiLCAiTlZEQSIpCmNvbG9ycyA9IGxpc3QoTUVUQSA9ICIjNWNjOWY1IiwgTlZEQSA9ICIjYjEzMWEyIikKCmNoYXJ0cyA8LSBtYXAodGlja2VycywgZnVuY3Rpb24oeCkgewogIAogIC5kdCA9IGQxICU+JSBmaWx0ZXIodGljID09IHgpCiAgCiAgaGNoYXJ0KC5kdCwgCiAgICAgICAgIHR5cGUgPSAiY29sdW1uIiwgCiAgICAgICAgIGhjYWVzKHggPSBhcy5jaGFyYWN0ZXIoZnllYXIpLCB5ID0gb2liZHAsIG5hbWUgPSBmeWVhcikpICAgJT4lCiAgICBoY19jb2xvcnMoY29sb3JzW1t4XV0pICU+JQogICAgaGNfeEF4aXModGl0bGUgPSBsaXN0KHRleHQgPSAiRmlzY2FsIFllYXIiKSkgJT4lCiAgICBoY195QXhpcyh0aXRsZSA9IGxpc3QodGV4dCA9ICJQcm9maXRzIGluICQgbWlsbGlvbiIpKSAlPiUKICAgIGhjX3RpdGxlKHRleHQgPSAuZHQkY29ubVsxXSkgJT4lCiAgICBoY19sZWdlbmQoZW5hYmxlZCA9IEZBTFNFKSAlPiUKICAgIGhjX3Rvb2x0aXAoaGVhZGVyRm9ybWF0ID0gIiIsCiAgICAgICAgICAgICAgIHBvaW50Rm9ybWF0ID0gJ1llYXI6IHtwb2ludC5uYW1lfTxicj48c3BhbiBzdHlsZT0iY29sb3I6e3BvaW50LmNvbG9yfSI+XHUyNzE0PC9zcGFuPiB7c2VyaWVzLm5hbWV9OiA8Yj57cG9pbnQueX08L2I+PGJyLz4nKSAlPiUKICAgIGhjX3Bsb3RPcHRpb25zKHNlcmllcyA9IGxpc3QoZGF0YUxhYmVscyA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIGZvcm1hdCA9ICJ7eX0iKSkpCn0pCgpgYGAKCmBgYHtyfQpod19ncmlkKGNoYXJ0cywgbmNvbCA9IDEsIHJvd2hlaWdodCA9IDYwMCkKYGBgCgoKCgo=