diff --git a/renv.lock b/renv.lock index 4087114..6de0439 100644 --- a/renv.lock +++ b/renv.lock @@ -133,6 +133,44 @@ ], "Hash": "45f0398006e83a5b10b72a90663d8d8c" }, + "RPostgres": { + "Package": "RPostgres", + "Version": "1.4.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "DBI", + "R", + "bit64", + "blob", + "cpp11", + "hms", + "lubridate", + "methods", + "plogr", + "withr" + ], + "Hash": "a3ccabc3de4657c14185c91f3e6d4b60" + }, + "RSQLite": { + "Package": "RSQLite", + "Version": "2.3.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "DBI", + "R", + "bit64", + "blob", + "cpp11", + "memoise", + "methods", + "pkgconfig", + "plogr", + "rlang" + ], + "Hash": "ae4a925e0f6bb1b7e5fa96b739c5221a" + }, "Rcpp": { "Package": "Rcpp", "Version": "1.0.12", @@ -144,6 +182,17 @@ ], "Hash": "5ea2700d21e038ace58269ecdbeb9ec0" }, + "RcppTOML": { + "Package": "RcppTOML", + "Version": "0.2.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "Rcpp" + ], + "Hash": "c232938949fcd8126034419cc529333a" + }, "askpass": { "Package": "askpass", "Version": "1.2.0", @@ -1554,6 +1603,23 @@ ], "Hash": "876c618df5ae610be84356d5d7a5d124" }, + "plogr": { + "Package": "plogr", + "Version": "0.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "09eb987710984fc2905c7129c7d85e65" + }, + "png": { + "Package": "png", + "Version": "0.1-8", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "bd54ba8a0a5faded999a7aab6e46b374" + }, "praise": { "Package": "praise", "Version": "1.0.0", @@ -1809,6 +1875,28 @@ ], "Hash": "1425f91b4d5d9a8f25352c44a3d914ed" }, + "reticulate": { + "Package": "reticulate", + "Version": "1.36.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Matrix", + "R", + "Rcpp", + "RcppTOML", + "graphics", + "here", + "jsonlite", + "methods", + "png", + "rappdirs", + "rlang", + "utils", + "withr" + ], + "Hash": "e037fb5dc364efdaf616eb6bc05aaca2" + }, "rex": { "Package": "rex", "Version": "1.2.1", diff --git a/screenshots/dbplyr.png b/screenshots/dbplyr.png new file mode 100644 index 0000000..450df99 Binary files /dev/null and b/screenshots/dbplyr.png differ diff --git a/screenshots/htmltools.png b/screenshots/htmltools.png new file mode 100644 index 0000000..33255c7 Binary files /dev/null and b/screenshots/htmltools.png differ diff --git a/screenshots/reticulate.png b/screenshots/reticulate.png new file mode 100644 index 0000000..220a9c7 Binary files /dev/null and b/screenshots/reticulate.png differ diff --git a/week10/index.qmd b/week10/index.qmd index 3a1be73..090ee0b 100644 --- a/week10/index.qmd +++ b/week10/index.qmd @@ -30,5 +30,13 @@ schedule |> ```{r} #| output: asis show_slides(week) +``` + +## Lab code + +Lab code can be downloaded here: [`lab.R`](lab.R) + +```{r} +#| output: asis show_assignments(week) ``` diff --git a/week10/lab.R b/week10/lab.R new file mode 100644 index 0000000..39bae1d --- /dev/null +++ b/week10/lab.R @@ -0,0 +1,205 @@ +library(rlang) +parse_expr("seq(1,10, by = 0.5)") +parse_expr("data |> mutate()") +parse_expr("data %>% mutate()") + + +myseq <- parse_expr("seq(1,10, by = 0.5)") +class(myseq) +new_function(list(), myseq) +eval(myseq) + +mycalc <- parse_expr("5 + 3 * 7") + +as.list(mycalc) + +5 + (3 * 7) + + +as.list(mycalc[[3]]) + +as.list(mycalc) +mycalc[[2]] <- 10 +mycalc + +parse_expr("`+`(5, 3*7)") +parse_expr("`+`(5, `*`(3,7))") + +mycalc +lobstr::ast(mycalc) +lobstr::ast(5 + 3 * 7) +as.list(mycalc) + + +lobstr::ast( + mtcars |> + group_by(cyl) |> + filter(mpg > 0.2) |> + mutate(mpg/wt) |> + ggplot() +) + +parse_expr( +"mtcars |> + group_by(cyl) |> + filter(mpg > 0.2) |> + mutate(mpg/wt) |> + ggplot()" +) + +lobstr::ast((-2)^2) +(-2)^2 + +lobstr::ast(!countries %in% c("Australia", "China")) + + +as.list(myseq) +call2("seq", 1L, 10, by = 0.5) +parse_expr(sprintf("seq(%i,%i, by = %f)", 1L, 10, 0.5)) + + +x / y +x <- expr(3 + 6) +y <- expr(1 + 2) + +call2("/", x, y) +parse_expr(sprintf("%s / %s", "3 + 6", "1 + 2")) + + +with( + list( + `+` = base::`-`, + `-` = base::`+` + ), + 3 + 8 +) + +library(rlang) +pkgs <- "rlang" +library(pkgs) + +purrr::map( + c("ggplot2", "dplyr", "tidyr"), + library, + character.only = TRUE +) + +purrr::map( + c("ggplot2", "dplyr", "tidyr"), + call2, .fn = "library" +) |> + purrr::map(eval) + + +library("ggplot2") + +mtcars |> select(cyl) +cyl + +readr::read_csv("data/study.csv") + +ggplot(mtcars, aes(mpg, wt)) + + geom_point() + +ggplot2:::`+.gg` +mpg +wt + +mtcars |> + mutate(wt/hp) + + +mtcars |> + left_join(mpg, by = c("model" = "car")) + +join_by + +1 + 1 + +base::`+` + +eval(sym("pi")) +sym("pi") +sym("mpg") +expr(1/pi) +lobstr::ast(1/pi) +quo(1/pi) +eval_tidy(quo(1/pi)) + +function(expr) { + expr <- enexpr(expr) +} + + +call2(sym("mutate"), sym("mtcars"), expr(wt/sym("hp"))) + +"mutate"() + +sym("2 * pi") + +mtcars |> + mutate(2 * mpg) |> + select("2 * mpg") + +expr(2 * pi) +lobstr::ast(2*pi) + +quo(2 * pi) + + +capture_expr <- function(x) { + x <- enquo(x) + pi <- 2.29 + eval_tidy(x) +} + +capture_expr(2*pi) +# expr(2*pi) + +myseq +lobstr::ast(myseq) +lobstr::ast(!!myseq) + +expr(!!pi) +expr(1/pi) +expr(1/!!pi) + +wt <- rnorm(32) +mtcars |> + summarise(weighted.mean(mpg, !!wt)) + +cyl <- 4 +mtcars |> + filter(cyl == !!cyl) + +var_summary(mtcars, cyl) + +syms(c("a", "b")) +exprs(a+b, a-b) +quos(a+b, a-b) + +var_summaries <- function(data, ...) { + vars <- enquos(...) + .min <- purrr::map(vars, ~ expr(min(!!.))) + names(.min) <- c("a", "b") + .max <- purrr::map(vars, ~ expr(max(!!.))) + data |> + summarise(n = n(), !!!.min, !!!.max) + # summarise(n = n(), a = min(mpg), b = min(wt), !!!.max) +} +mtcars |> + group_by(cyl) |> + var_summaries(mpg, wt) + + + +cyl <- 4 +mtcars |> + filter(cyl == !!cyl) +mtcars |> + filter(.data$pi == .env$cyl) + +mtcars |> + filter(.data$pi == .env$cyl) + +pi diff --git a/week10/slides.qmd b/week10/slides.qmd index 6ed54b4..4b6a7eb 100644 --- a/week10/slides.qmd +++ b/week10/slides.qmd @@ -267,6 +267,7 @@ Do the following functions use standard evaluation or NSE? * `a + b * c` * `mtcars |> select(cyl)` * `read_csv("data/study.csv")` +* `ggplot() + geom_line()` * `mtcars |> mutate(wt/hp)` * `with(mtcars, wt/hp)` ::: diff --git a/week11/earth.jpg b/week11/earth.jpg new file mode 100644 index 0000000..3b3447c Binary files /dev/null and b/week11/earth.jpg differ diff --git a/week11/index.qmd b/week11/index.qmd index 3f6fd23..72d13a1 100644 --- a/week11/index.qmd +++ b/week11/index.qmd @@ -26,5 +26,14 @@ schedule |> ```{r} #| output: asis show_slides(week) +``` + + +## Lab code + +Lab code can be downloaded here: [`lab.R`](lab.R) + +```{r} +#| output: asis show_assignments(week) ``` diff --git a/week11/lab.R b/week11/lab.R new file mode 100644 index 0000000..440162a --- /dev/null +++ b/week11/lab.R @@ -0,0 +1,73 @@ +library(reticulate) + +os <- reticulate::import("os") +# os.abort() +# os$abort() + +files <- os$listdir() +readLines(files[1]) + +numpy <- import("numpy", convert = FALSE) + +a <- numpy$array(c(1:4)) +a +a$length +b <- a$cumsum() +a$size +class(a) + +py_to_r(b) +r_to_py(1:4) + +# py_install("matplotlib") +# reticulate::install_python() + +r_to_py(1) +r_to_py(list(1)) +r_to_py(1:10) +r_to_py(as.list(1:10)) + + +r_to_py(mtcars) + +library(dplyr) +con <- DBI::dbConnect(RSQLite::SQLite(), host = ":memory:") +DBI::dbListTables(con) +copy_to(con, mtcars) +DBI::dbWriteTable(con, "mtcars", mtcars) + +DBI::dbListTables(con) + +db_mtcars <- tbl(con, "mtcars") +class(db_mtcars) + + +db_mtcars |> + filter(cyl == 4) |> + summarise(hp = mean(hp, na.rm=TRUE)) |> + collect() +DBI::dbDisconnect(con) + +con + + +con <- DBI::dbConnect( + RPostgres::Postgres(), + dbname = "arp", + host = "arp.nectric.com.au", port = "5432", + user = "monash", password = "arp2024" +) +con +# Host: arp.nectric.com.au:5432 +# Username: monash +# Password: arp2024 +# Database: arp + + +DBI::dbListTables(con) +tbl(con, "penguins") |> + group_by(species) |> + summarise(avg_weight_g = mean(body_mass_g, na.rm = TRUE)) |> + collect() + +usethis::create_package("wordcloud2") diff --git a/week11/lovewords.csv b/week11/lovewords.csv new file mode 100644 index 0000000..b6aa517 --- /dev/null +++ b/week11/lovewords.csv @@ -0,0 +1,93 @@ +word,freq +Love,60 +Liebe,30 +ፍቅር,15 +Lufu,15 +حب,25 +Aimor,15 +Amor,25 +Heyran,25 +ভালোবাসা,15 +Каханне,20 +Любоў,15 +Любов,25 +བརྩེ་དུང་།,20 +Ljubav,25 +Karantez,20 +Юрату,15 +Láska,15 +Amore,25 +Cariad,15 +Kærlighed,25 +Armastus,25 +Αγάπη,15 +Amo,15 +Amol,20 +Maitasun,15 +عشق,15 +Pyar,20 +Amour,25 +Leafde,15 +Gràdh,15 +愛,15 +爱,25 +પ્રેમ,20 +사랑,20 +Սեր,15 +Ihunanya,15 +Cinta,15 +ᑕᑯᑦᓱᒍᓱᑉᐳᖅ,15 +Ást,15 +אהבה,20 +ಪ್ರೀತಿ,20 +სიყვარული,25 +Махаббат,15 +Pendo,25 +Сүйүү,25 +Mīlestība,15 +Meilė,25 +Leefde,20 +Bolingo,20 +Szerelem,25 +Љубов,15 +സ്നേഹം,20 +Imħabba,20 +प्रेम,20 +Ái,20 +Хайр,15 +အချစ်,25 +Tlazohtiliztli,15 +Liefde,15 +माया,15 +मतिना,20 +Kjærlighet,25 +Kjærleik,20 +ପ୍ରେମ,15 +Sevgi,15 +ਪਿਆਰ,20 +پیار,25 +Miłość,25 +Leevde,20 +Dragoste,25 +Khuyay,15 +Любовь,25 +Таптал,25 +Dashuria,20 +Amuri,25 +ආදරය,15 +Ljubezen,15 +Jaceyl,20 +خۆشەویستی,20 +Љубав,20 +Rakkaus,15 +Kärlek,20 +Pag-ibig,20 +காதல்,25 +ప్రేమ,25 +ความรัก,15 +Ишқ,25 +Aşk,20 +محبت,15 +Tình yêu,15 +Higugma,25 +ליבע,25 diff --git a/week11/pyplot.png b/week11/pyplot.png new file mode 100644 index 0000000..162e5db Binary files /dev/null and b/week11/pyplot.png differ diff --git a/week11/slides.qmd b/week11/slides.qmd new file mode 100644 index 0000000..dfc4e62 --- /dev/null +++ b/week11/slides.qmd @@ -0,0 +1,694 @@ +--- +title: ETC4500/ETC5450 Advanced R programming +author: "Week 11: Interfacing with other languages" +format: + beamer: + pdf-engine: pdflatex + aspectratio: 169 + fontsize: "14pt,t" + section-titles: false + knitr: + opts_chunk: + dev: "cairo_pdf" + fig-width: 7.5 + fig-height: 3.5 + include-in-header: ../header.tex + keep-tex: false +--- + +```{r} +#| label: setup +#| include: false +#| cache: false +source(here::here("setup.R")) +source(here::here("course_info.R")) +``` + +## Outline + +\vspace*{0.4cm} +\tableofcontents + + +# Unit updates + +## Assignment 4 + +* Assignment 4 due 24 May + +Any questions? + +## SETUs + +**SETUs are now open.** + +Please complete your SETU, we make improvements based on your feedback. + +This is especially important for us in this unit, since it is our first year running the unit! + + + +# R as an interface language + +## R as an interface language + +R is a powerful design language, with lots of flexibility for creating good (or bad) programming interfaces. + +Much of R is built up on libraries from other languages, and R's flexible interface design makes them easy to use. + + + + + +## R Core + +R itself is mostly written using different programming languages (mostly C and Fortran). + +You can find the source code for R at , or mirrored on GitHub at + +## Wrapper functions and abstractions + +The use of abstraction and wrapping other software is fundamental to programming. + +Wrapper functions call a second function with minimal/no change to the output. They are used to adapt existing code to work for a new design or programming language. + +Wrappers often involve abstraction, a process of reducing complexity by simplifying the user-facing function's design. + + +## Wrapping functions with NSE + +Last week we saw how non-standard evaluation (NSE) can take any syntactically valid R code and evaluate it differently. + +Metaprogramming is often used to directly translate R code into other languages. + +# Interfacing other programming languages + +## Interfacing other programming languages + +An interface to a different programming language involves: + +* Designing an R interface which can be translated into code for the other language +* Converting objects to and from each language +* Passing side-effects (like image output) + +## Interfacing Python with reticulate + +\placefig{13.5}{0.5}{width=2cm}{../screenshots/reticulate.png} + +```{r} +library(reticulate) +``` + +The `reticulate` package allows Python to run from within R. + +* Translates R syntax to Python +* Converts R objects to Python +* Converts Python objects to R + +The Python version and package environment can be set with: + +```{r} +#| eval: false +use_python("/usr/local/bin/python") +``` + +## Python example from R with reticulate + +\fontsize{10}{10}\sf +```{r} +#| eval: false +# reticulate::py_install("numpy") +np <- import("numpy", convert = FALSE) + +# do some array manipulations with NumPy +a <- np$array(c(1:4)) +a +``` + +```{r} +#| echo: false +r_to_py(1:4) +``` + +```{r} +#| eval: false +sum <- a$cumsum() +sum +``` + +```{r} +#| echo: false +r_to_py(c(1L,3L,6L,10L)) +``` + +```{r} +#| eval: false +# convert to R explicitly at the end +py_to_r(sum) +``` + +```{r} +#| echo: false +c(1L,3L,6L,10L) +``` + + +## Converting objects between R and Python + +Full conversion table here: [*Calling Python*](https://rstudio.github.io/reticulate/articles/calling_python.html#type-conversions) + +\fontsize{10}{10}\sf +```{r} +r_to_py(1) +r_to_py(1:10) +r_to_py(list(norm = rnorm(10), pois = rpois(10, 3))) +r_to_py(mtcars) +``` + +## Plots (and other side-effects) + +\fontsize{10}{10}\sf +```{r} +#| eval: false +plt <- import("matplotlib.pyplot") +fig <- plt$figure(figsize=c(14,8)) +x <- seq(-3, 3, by = 0.01) +plt$plot(x,dnorm(x)) +plt$show() +``` + +![](pyplot.png){height=50%} + + +## Interfacing other programming languages + +* Any system commands with `system()` +* C/C++: Directly in R with `.Call()` or Rcpp (next week!) +* Julia: JuliaCall +* Matlab/Octave: R.matlab +* Stata: RStata +* JavaScript: V8 +* Java: rJava +* Lua: luajr + +# Data analysis with databases + +## Data analysis with databases + +Often data for analysis is stored and used within a database. + +A database is an efficient way of securely storing and interacting with large datasets. + +It is also a good technique for working with data that is too large to fit in memory. + +## dbplyr + +\placefig{13.5}{0.5}{width=2cm}{../screenshots/dbplyr.png} + +```{r} +library(dbplyr) +``` + + +The dbplyr package allows you to use dplyr code to manipulate tables from databases. + +It achieves this using non-standard evaluation to convert dplyr and R code into suitable database code for a connected database. + +## dbplyr backends + +Backends are interfaces between R and database languages. + +There are many database backends available for dbplyr: + +:::: {.columns} + +::: {.column width="50%"} +* MySQL / SQLite +* Snowflake +* PostgreSQL +* Spark +* ODBC +* MS Access +::: + +::: {.column width="50%"} +* SAP HANA +* Hive +* Impala +* Oracle +* Redshift +* Teradata +::: + +:::: + +## Creating a database + +You can quickly create a SQLite database in memory with: + +```{r} +#| cache: false +con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") +con +``` + +Currently this database doesn't contain any tables: + +```{r} +DBI::dbListTables(con) +``` + +## Using a database + +We can add a dataset to the database from R with: + +\fontsize{10}{10}\sf +```{r} +#| cache: false +copy_to(con, mtcars) +DBI::dbListTables(con) +``` + +\fontsize{14}{10}\sf +You can then retrieve the table using `tbl()` + +\fontsize{10}{10}\sf +```{r} +tbl(con, "mtcars") +``` + +## Manipulating a table + +With the database table object, you can use dplyr: + +\fontsize{10}{10}\sf +```{r} +tbl(con, "mtcars") |> + group_by(cyl) |> + summarise(mean(mpg), mean(hp)) +``` + +## Collecting the results + +When your dplyr data manipulation is complete, you can then `collect()` your results as a regular R data frame for use with other packages. + +\fontsize{10}{10}\sf +```{r} +tbl(con, "mtcars") |> + group_by(cyl) |> + summarise(mean(mpg), mean(hp)) |> + collect() +``` + +## Disconnecting from a database + +Once finished, it is good practice to disconnect from the database using `DBI::dbDisconnect()`: + +```{r} +#| cache: false +#| error: true +DBI::dbDisconnect(con) +``` + +See now that the database is disconnected: + +```{r} +con +``` + + +## Connecting to a remote database + +In most cases you will be connecting to a remove database. + +Here's the credentials to a PostgreSQL database containing some very important data: + +* Host: arp.nectric.com.au:5432 +* Username: monash +* Password: arp2024 +* Database: arp + +## Connecting to a remote database + +::: {.callout-caution title="Your turn!"} +Connect to the remote database and use the data. + +\vspace{1em} + +Hint: the connection code looks like this: + +```{r} +#| eval: false + +con <- DBI::dbConnect( + RPostgres::Postgres(), + dbname = "???", + host = "???", port = "???", + user = "???", password = "???" +) +``` + +```{r} +#| cache: false +#| include: false + +con <- DBI::dbConnect( + RPostgres::Postgres(), + dbname = "arp", + host = "arp.nectric.com.au", port = "5432", + user = "monash", password = "arp2024" +) +``` + +::: + + +## Using data on a remote database + +As before, dbplyr allows you to manipulate tables using dplyr code. + +\fontsize{10}{10}\sf +```{r} +tbl(con, "penguins") |> + group_by(species) |> + summarise(avg_mass_g = mean(body_mass_g, na.rm = TRUE)) +``` + +\fontsize{14}{10}\sf +All database operations are done on the remote server. + +## How it works - translating expressions + +dbplyr uses NSE to translate R code into SQL / database code. + +You can try this out directly with `translate_sql()`: + +\fontsize{10}{10}\sf +```{r} +translate_sql(mean(body_mass_g, na.rm = TRUE), con = con) +translate_sql(x ^ 2L, con = con) +translate_sql(substr(x, 5, 10), con = con) +``` + +## How it works - translating expressions + +Not all R functions can be translated to database queries. + +Consider `logp1()`, it gets translated directly as SQL: + +\fontsize{10}{10}\sf +```{r} +translate_sql(logp1(x), con = con) +``` + +\fontsize{14}{10}\sf +However this doesn't work, `log(body_mass_g + 1)` does. + +\fontsize{10}{10}\sf +```{r} +#| error: true +tbl(con, "penguins") |> + mutate(logp1(body_mass_g)) +``` + + +## How it works - translating expressions + +Not all database queries can be written in R. + +For this you can write literal `SQL` commands with the `sql()` function. + +\fontsize{10}{10}\sf +```{r} +translate_sql(sql("x!"), con = con) +translate_sql(x == sql("ANY VALUES(1, 2, 3)"), con = con) +``` + + +## How it works - translating dplyr verbs + +For any chain of dplyr commands, you can find the SQL / database query by using `show_query()` instead of `collect()`. + +```{r} +tbl(con, "penguins") |> + group_by(species) |> + summarise(avg_mass_g = mean(body_mass_g, na.rm = TRUE)) |> + show_query() +``` + +# Creating interactive web components + +## Creating interactive web components + +In week 8 we saw how reactive programming can add interactivity to web applications using shiny. + +Today we'll see how to use R and Javascript to create interactive UI elements. + +:::{.callout-tip title="Concepts combined"} + +The UI elements from today and the reactive server code from week 8 is all the ingredients to create shiny apps. + +::: + +## Shiny extensions + +There are many JS libraries which have been wrapped up into R packages, for use in Shiny or regular analysis. + + + +## htmltools + +\placefig{13.5}{0.5}{width=2cm}{../screenshots/htmltools.png} + +```{r} +library(htmltools) +``` + +The htmltools package allows you to write HTML code with R. + +```{r} +div( + p("Hello world!"), + img(src = "earth.jpg") +) +``` + +:::: {.columns} + +::: {.column width="50%"} +```html +
+

Hello world!

+ +
+``` + +::: + +::: {.column width="50%"} +Hello world! + +![](earth.jpg){width="50%"} +::: + +:::: + +## htmltools + +This is used to create the UI of a Shiny app. + +It is also include the necessary CSS/JS dependencies for HTML reports with interactive 'widgets'. + +## htmlwidgets + +The htmlwidgets package provides a framework for creating R bindings to JavaScript libraries. HTML Widgets can be: + +* Used at the R console for data analysis just like conventional R plots. +* Embedded within R Markdown documents +* Incorporated into Shiny web applications. +* Saved as standalone web pages for ad-hoc sharing via email, file transfer, web deployment, etc. + +## htmlwidgets showcase + +The htmlwidgets package powers many popular R packages including: + +* leaflet +* plotly +* visNetwork +* DiagrammeR + + + +## htmlwidgets components + +All widgets include the following components: + +* Web dependencies: JS and CSS assets used by the widget +* R binding: This is the function that users call to create the output +* JavaScript binding: The JavaScript code that glues everything together, passing data/options from the R binding to the underlying JavaScript library. + +## htmlwidgets setup + +From within a package, you can quickly get started with a htmlwidget using: + +```r +htmlwidgets::scaffoldWidget("mywidget") +``` + +::: {.callout-caution title="Follow along!"} + +Create a package for making interactive wordclouds. + +We'll use the wordcloud2.js library, available on GitHub here: +::: + + +## htmlwidgets setup + +The htmlwidgets components are organised in packages with this file structure: + +\fontsize{10}{10}\sf +``` +R/ +| .R + +inst/ +|-- htmlwidgets/ +| |-- .js +| |-- .yaml +| |-- lib/ +| | |-- / +``` + +## Web dependencies + +Dependencies are specified using the YAML configuration file located at `inst/htmlwidgets/.yaml`. + +```yaml +dependencies: + - name: + version: + src: htmlwidgets/lib/ + script: + - + stylesheet: + - +``` + +## Web dependencies + +::: {.callout-caution title="Follow along!"} +Download the JavaScript src for wordcloud2.js and add it to the package as a htmlwidgets dependency. + +The JavaScript library's sources are available in the repository's `src/` folder. + + +::: + +## R binding + +An R function which returns a htmltools widget created with `htmlwidgets::createWidget()` + +```r +function(x, ...) { + # R code preparing data/settings + + # Return a HTML widget + createWidget( + name, # The name of your widget in /inst + x, # The data/settings for the widget's JS binding + ... + ) +} +``` + +## R binding + +::: {.callout-caution title="Follow along!"} +Update the generated R binding function to: + +* Accept a character vector of words. +* Accept a numeric vector of frequency/weight. +* Pass these inputs into the htmlwidget via `x`. + +\vspace{1em} + +Bonus: improve the design by accepting `.data` as the first input, then using tidy evaluation to pass in the words and frequencies from `.data`. +::: + +## JavaScript binding + +The JavaScript code that takes data/settings from R and uses the JS library to create the output. + +\fontsize{10}{10}\sf +```js +HTMLWidgets.widget({ + name: "", + type: "output", + factory: function(el, width, height) { + // initialise the JavaScript object from the library here + var obj = new ; + return { + renderValue: function(x) { + // update the initalised JavaScript object with new data/settings + }, + resize: function(width, height) { + // Re-render or otherwise update size when window changes + } + }; + } +}); +``` + +## JavaScript binding + +::: {.callout-caution title="Follow along!"} +Update the generated JavaScript binding to create the wordcloud on the htmlwidgets HTML element `el`. + +\vspace{1em} + +Hint: a wordcloud is created using `wordcloud2.js` with: + +```js +WordCloud(el, { list: [['foo', 12], ['bar', 6]] } ); +``` + +Hint: The data can be transposed from two separate vectors into the above format with `HTMLWidgets.transposeArray2D([x.words, x.freqs])` +::: + + +## Create a wordcloud + +::: {.callout-caution title="Your turn!"} +Your wordcloud function is now ready to use, try it out! + +\vspace{1em} + +You can try it with the love words example dataset here: + +```{r} +#| eval: false + +readr::read_csv( + "https://arp.numbat.space/week11/lovewords.csv" +) +``` + +::: + +## Use the wordclouds in a shiny app + +The bindings for shiny apps are already created by `htmlwidgets::scaffoldWidget()`, and can be used in shiny like any other UI output and server renderer. + +```{r} +widgetOutput <- function(outputId, width = '100%', height = '400px'){ + htmlwidgets::shinyWidgetOutput(outputId, 'widget', width, height, + package = 'package') +} + +renderWidget <- function(expr, env = parent.frame(), quoted = FALSE) { + if (!quoted) { expr <- substitute(expr) } # force quoted + htmlwidgets::shinyRenderWidget(expr, widgetOutput, env, quoted = TRUE) +} +``` +