Top 3 Tools to Monitor User Adoption in R Shiny

Reading time:
time
min
By:
Dario Radečić
May 24, 2022

Can you monitor user adoption for R Shiny apps? What is <i>user adoption</i> anyway? We'll answer these questions and show you how to do it yourself in this article.

Put simply, user adoption is the process by which new users become familiar with your product and/or service and decide to keep using it. User adoption rate is critical for your R Shiny dashboards because it tells you if your service actually solves a problem. If the adoption rate is high, more users are adopting your dashboard than abandoning it.

But what tools can you use to monitor user adoption in R Shiny? Today we bring you 3 tools to include in your next project.
<blockquote>Not an R Shiny expert? <a href="https://appsilon.com/dashboards-in-rshiny/" target="_blank" rel="noopener">Here are example dashboards you can build with Shiny</a>.</blockquote>
Table of contents:
<ul><li><a href="#shiny-stats">Shiny.stats - Add Statistics Panel to R Shiny</a></li><li><a href="#shiny-logs">Shinylogs - Track Inputs, Outputs, Errors, and Session Info</a></li><li><a href="#google-analytics">Google Analytics - It Works with R Shiny Too</a></li><li><a href="#summary">Summary</a></li></ul>

<hr />

<h2 id="shiny-stats">Shiny.stats (now shiny.telemetry)- Add Statistics Panel to R Shiny</h2>
The <code>shiny.stats</code> package by <a href="https://github.com/Appsilon/shiny.stats" target="_blank" rel="noopener">Appsilon</a> (renamed to shiny.telemetry and available on CRAN) provides an easy way for logging users' activity and adding a statistics panel to your R Shiny apps. The only requirement is that you have an accessible database, which can be as simple as a local PostgreSQL database or a sqlite file.

Before you can start monitoring user adoption with <code>shiny.stats</code>, you'll have to install the package. Install it directly from GitHub:
<pre>library(devtools)
install_github("Appsilon/shiny.stats")</pre>
The next step is to initialize the database you want to use with <code>shiny.stats</code>. <b>You should only do this once.</b> The snippet below will connect to a sqlite database named <code>user_stats</code>:
<pre>library(DBI)

connection &lt;- DBI::dbConnect(RSQLite::SQLite(), dbname = "user_stats.sqlite")
DBI::dbDisconnect(connection)</pre>
Now comes the potentially tricky part, depending on how your app's authentication is set up. You'll need to define a function to extract the username. The below example will extract it from the URL, from a dedicated <code>username</code> parameter.

Once that's out of the way, plug the logic into the Shiny app. The magic happens in the <code>server</code> function where the database connection is established and user behavior is monitored:
<pre>library(shiny)
library(shiny.stats)
library(RSQLite)


get_user &lt;- function(session) {
 parseQueryString(isolate(session$clientData$url_search))$username
}

ui &lt;- fluidPage(
 titlePanel("Old Faithful Geyser Data"),
 sidebarLayout(
   sidebarPanel(
     sliderInput("bins",
       "Number of bins:",
       min = 1,
       max = 50,
       value = 30
     ),
     actionButton("apply_slider", "Apply")
   ),
   mainPanel(
     plotOutput("distPlot")
   )
 )
)

server &lt;- function(input, output, session) {
 connection &lt;- odbc::dbConnect(RSQLite::SQLite(), dbname = "user_stats.sqlite")

 # creating user connection list and making sure required tables exist in DB
 user_connection &lt;- initialize_connection(connection, username = get_user(session))

 # registering login
 log_login(user_connection)

 # selecting registered actions to watch
 log_click(user_connection, id = "apply_slider")
 log_input(user_connection, input, input_id = "bins")

 # server code
 output$distPlot &lt;- renderPlot({
   input$apply_slider
   x &lt;- faithful[, 2]
   bins &lt;- seq(min(x), max(x), length.out = isolate(input$bins) + 1)
   hist(x, breaks = bins, col = "darkgray", border = "white")
 })

 # registering logout (this also disconnects connection object, if not used take care of it on your own)
 log_logout(user_connection)
}

shinyApp(ui = ui, server = server, options = list(port = 8888, launch.browser = FALSE))</pre>
Here's what the Shiny app looks like:

<img class="size-full wp-image-12855" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3f5d39529106a404233_128f5424_1.webp" alt="Image 1 - R Shiny app that monitors user adoption data" width="2424" height="1860" /> Image 1 - R Shiny app that monitors user adoption data

You simply can't tell that user behavior is monitored from the app itself. Luckily, we can check the database. Open the sqlite file in your favorite database management tool - we're using <a href="https://tableplus.com/" target="_blank" rel="noopener">TablePlus</a>:

<img class="size-full wp-image-12857" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3f6b6953e1bff2614c2_559a3de6_2.webp" alt="Image 2 - User behaviour data saved in the database" width="2494" height="1626" /> Image 2 - User behavior data saved in the database

Do you know what's really handy about <code>shiny.stats</code>? You can easily display users' stats in an R Shiny dashboard. The following code snippet creates a dashboard that allows you to monitor user adoption in R Shiny:
<pre>library(shiny)
library(RSQLite)
library(shiny.stats)

# prepare credentials list to access logs:
db_credentials &lt;- list(
 DB_NAME = "user_stats.sqlite",
 DB_DRIVER = "SQLite"
)

# define function hot to get username
get_user &lt;- function(session) {
 username &lt;- isolate(parseQueryString(session$clientData$url_search)$username)
 req(username)
 return(username)
}

# define ui and server
ui &lt;- shiny_stats_ui()
server &lt;- shiny_stats_server(get_user, db_credentials = db_credentials)

shinyApp(ui = ui, server = server, options = list(port = 8887, launch.browser = FALSE))</pre>
<img class="size-full wp-image-12859" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3f74b197a92857f623e_7548217c_3.webp" alt="Image 3 - Displaying users' data in R Shiny dashboard" width="2980" height="2144" /> Image 3 - Displaying users' data in an R Shiny dashboard

Easy, right? Let's see what else is available in R Shiny.
<h2 id="shiny-logs">Shinylogs - Track Inputs, Outputs, Errors, and Session Info</h2>
The <code>shinylogs</code> is another <a href="https://dreamrs.github.io/shinylogs/index.html" target="_blank" rel="noopener">package</a> that allows you to monitor user adoption in R Shiny. It records inputs or output changes, and info about the user's session. All recordings are done client-side, so you can rest assured it won't slow down the application and occupy the server.

As with <code>shiny.stats</code>, the first step here is to install the package. It's available on CRAN which makes things super simple:
<pre>install.packages("shinylogs")</pre>
The way <code>shinylogs</code> handles storing user monitoring data by combining two functions. The first one - <code>track_usage()</code> captures all inputs, errors, outputs, and session info, and it also accepts the second function as a parameter. The name of that function varies. For example, you can use <code>store_json(path = "")</code> to save logs in JSON format. Other formats are also available, such as RDS, Google Drive, SQLite, and others. Read their <a href="https://dreamrs.github.io/shinylogs/reference/index.html" target="_blank" rel="noopener">function reference</a> for more details.

We'll keep things simple and store logs in JSON format in the <code>logs/</code> directory. The R Shiny app below applies a clustering algorithm to the Iris dataset and lets you change the columns you want to see on a scatter plot:
<pre>library(shiny)
library(shinylogs)

ui &lt;- fluidPage(
 headerPanel("Iris k-means clustering"),
 sidebarLayout(
   sidebarPanel(
     selectInput(
       inputId = "xcol",
       label = "X Variable",
       choices = names(iris)
     ),
     selectInput(
       inputId = "ycol",
       label = "Y Variable",
       choices = names(iris),
       selected = names(iris)[[2]]
     ),
     numericInput(
       inputId = "clusters",
       label = "Cluster count",
       value = 3,
       min = 1,
       max = 9
     )
   ),
   mainPanel(
     plotOutput("plot1")
   )
 )
)

server &lt;- function(input, output, session) {
 # Store JSON with logs in the temp dir
 track_usage(
   storage_mode = store_json(path = "logs/")
 )

 # classic server logic
 selectedData &lt;- reactive({
   iris[, c(input$xcol, input$ycol)]
 })
 clusters &lt;- reactive({
   kmeans(selectedData(), input$clusters)
 })
 output$plot1 &lt;- renderPlot({
   palette(c(
     "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",
     "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"
   ))

   par(mar = c(5.1, 4.1, 0, 1))
   plot(selectedData(),
     col = clusters()$cluster,
     pch = 20, cex = 3
   )
   points(clusters()$centers, pch = 4, cex = 4, lwd = 4)
 })
}

shinyApp(ui = ui, server = server)</pre>
Here's what the Shiny app looks like:

<img class="size-full wp-image-12861" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3f8f9a88e2eece0938d_7a79076c_4.webp" alt="Image 4 - R Shiny application that uses shinylogs in the background" width="2520" height="1946" /> Image 4 - R Shiny application that uses shinylogs in the background

Just make a couple of adjustments here and there in the dashboard - change the columns, cluster numbers, go crazy! The logs will automatically get saved to the <code>logs/</code> directory, as you can see from the image below:

<img class="size-full wp-image-12863" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3f94b197a92857f6506_2176fd8e_5.webp" alt="Image 5 - Contents of the logs/ directory" width="2064" height="1096" /> Image 5 - Contents of the logs/ directory

The logs are saved in JSON format, which means you can grab the contents and open any JSON formatter available online. Doing so will pretty-print the JSON, meaning it will be easier to read:

<img class="size-full wp-image-12865" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3fa9781259e9855a399_583af839_6.webp" alt="Image 6 - Contents of the log file, pretty printed" width="2280" height="2330" /> Image 6 - Contents of the log file, pretty printed

Did you see how easy that was? You didn't have to configure anything, besides the storage file format option and the path. Up next, we'll explore one massively popular option to monitor user adoption in R Shiny.
<h2 id="google-analytics">Google Analytics - It Works with R Shiny Too</h2>
Setting up Google Analytics to work with R Shiny is much more challenging than the previous two options. You'll first need to create a <a href="https://analytics.google.com/" target="_blank" rel="noopener">Google Analytics account</a> if you don't already have it and then create a <a href="https://shiny.rstudio.com/articles/google-analytics.html#step-2---add-a-property" target="_blank" rel="noopener">new property</a>. The linked article provides a step-by-step guide, so we'll only focus on the good parts.

Assuming you have an Analytics account registered and property created, download these four files from <a href="https://github.com/rstudio/shiny-examples/tree/main/181-google-analytics" target="_blank" rel="noopener">GitHub</a>:

<img class="size-full wp-image-12867" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3fb8682e8fbf371c9a0_614a934e_7.webp" alt="Image 7 - GitHub contents of the R Shiny app" width="2280" height="2014" /> Image 7 - GitHub contents of the R Shiny app

These four files are everything we need to link an R Shiny application to Google Analytics, and therefore, monitor user adoption. Now head back to Google Analytics and copy the Global Site Tag (gtag.js) script. It should look like this:

<img class="size-full wp-image-12869" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3fb2c764ec4770fbd78_4ced4541_8.webp" alt="Image 8 - Global Site Tag script" width="2280" height="1930" /> Image 8 - Global Site Tag script

You'll need to partially replace the contents of <code>google-analytics.html</code> with your global site tag info. Here's what ours looks like after the modification:
<pre>&lt;!-- Global site tag (gtag.js) - Google Analytics --&gt;
&lt;script async src="https://www.googletagmanager.com/gtag/js?id="&gt;
&lt;script&gt;
 window.dataLayer = window.dataLayer || [];
 function gtag(){dataLayer.push(arguments);}
 gtag('js', new Date());

 gtag('config', '');

 $(document).on('change', 'select', function(e) {
   ga('send', 'event', 'widget', 'select data', $(e.currentTarget).val());
 });

 $(document).on('click', 'button', function() {
   ga('send', 'event', 'button', 'plot data');
 });
&lt;/script&gt;
</pre>
You can use ours, of course, just remember to change the global site tag ID.

Once that's out of the way, run <code>app.R</code> to verify Shiny app loads without any issues:

<img class="size-full wp-image-12871" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3fc9781259e9855a632_585faf8d_9.webp" alt="Image 9 - Sunlight in the US R Shiny app" width="2182" height="2022" /> Image 9 - Sunlight in the US R Shiny app

Neat. Everything works as advertised, and the R Shiny app is now automatically logging everything you do into Google Analytics. <b>Keep in mind: </b> Changes won't be reflected immediately in Google Analytics - please allow a couple of minutes for usage data to become visible.

Here's what it looks like on our end. The first image shows real-time info:

<img class="size-full wp-image-12873" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3fdbc9849746c74a9d6_ce6bdd20_10.webp" alt="Image 10 - Google Analytics realtime overview" width="2280" height="2014" /> Image 10 - Google Analytics real-time overview

And the following one shows an overview of the engagement:

<img class="size-full wp-image-12875" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b2ac10598db7fd4c6fd0be_11.webp" alt="Image 11 - Google Analytics engagement overview" width="2280" height="2014" /> Image 11 - Google Analytics engagement overview

That's it! R Shiny is now connected to Google Analytics.

<hr />

<h2 id="summary">Monitor User Adoption in R Shiny - Summary</h2>
In short, yes you can monitor user adoption in R Shiny apps. And there are multiple ways to do so. In this article, we covered 3 quick ways to monitor user adoption in R Shiny. The first two solutions are a no-brainer for small apps and teams, especially if you don't have to monitor everything. The Google Analytics solution is a clear winner and an industry standard if your app has many users and you really want to make deep dives into user behavior. It's a bit more challenging to set up, but nothing you can't manage in a coffee break.

Which tool do you use to monitor user adoption in R Shiny apps? Please let us know in the comment section below. Also, don't hesitate to hit us on Twitter - <a href="https://twitter.com/appsilon" target="_blank" rel="noopener">@appsilon</a>. We'd love to hear your input.
<blockquote>Want to share your R Shiny apps with the world? <a href="https://appsilon.com/how-to-share-r-shiny-apps/" target="_blank" rel="noopener">Here are 3 ways to Share R Shiny apps, entirely free</a>.</blockquote>

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!

Is Your Software GxP Compliant?

Download a checklist designed for clinical managers in data departments to make sure that software meets requirements for FDA and EMA submissions.
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
r
shiny
tutorials