
Data is more engaged with storytelling. As a data professional, I seek less complicated ways to convey the gap between data analysis and communication. A dashboard is traditionally the default way to visualize and share data. It also carries the responsibility for communication. However, I found the limitations of the dashboard: limited chart selections and less freedom to customize. I explored more interactive ways to illustrate ideas and engage with users — build a web application for your data.
Shiny is an R package for building interactive web apps directly from R code. Using R without worrying about context switching into HTML, CSS, and JavaScript is exciting. This saves time for consistently being in one language. We can reuse the R’s data frame and manipulate data using R packages like dply and ggplot2 charts without issue. Hadley Wickham’s book — Mastering Shiny has in-depth content on this.
What’s more exciting about using R and Shiny is now we can build an app where the end users can interact with the data analysis we have done. They can gain more insight by exploring alone without knowing the complication behind the scene. In this story, I will show you how to engage with users by storytelling — show data analytics in R and Shiny.
Prepare Data for building the Cocomelon Video.
In my previous story, I shared how we can use the Youtube data API in R to retrieve the statistics of the world’s second-largest Youtube channel — Cocomelon. We have built a process to retrieve data, perform basic cleaning up, and deliver several data visualizations using ggplot2. Throughout this analysis, there are a couple of interesting findings:
- The most popular Cocomelon videos created 4-5 years ago remain trending up due to many daily watches.
- The most watched video: “Bath Song,” is played 20%+ more times than the second video — ”Wheels on the Bus.”
- The most-watched video for videos released in 200 days is 🔴 CoComelon Songs Live 24/7. This video shows that parents can keep the kids with videos automatically rotating without switching videos explicitly.
Using data to tell this story is interesting. However, as the end users, I assume you are familiar with R and some R packages for data analysis.
What if you don’t know anything above? As a parent, I found it exciting data to help your little ones not get bored with the “always popular” videos. Is there an easy way to show the data to those who know nothing about R or any programming language? And can we let more users explore themselves?
Yes. I can build a web App and share it publicly to make everyone experience the web app built by R and Shiny
I wrote down my process on how to build this end-to-end web app with Shiny in R. (If you don’t care about how to make it technically, scroll to the bottom and see the final result)
Inspired By The Book - Mastering Shiny
I knew nothing about Shiny until I read Mr. Wickham’s book, Mastering Shiny. Mr. Wickham is the Chief Scientist at RStudio, and his books are all famous in the R community. Picking a specific package to write as a book, Shiny is a unique project in the R community.
Let users interact with your data and your analysis — Shiny
Storytelling with data should not be in one direction. It deserved to be bi-directional. With user engagement, it helps with the mutual understanding of data, and it becomes easier to achieve the goal with the data analysis you produce and reduce the communication barrier.
The Shiny package breaks the gap between the data professional who performs analysis and the data consumer who leverages the data to make the decision. It makes a perfect use case for users to engage with the data analysis you have done by enabling one element: Interactivity.
Note: This story isn’t Shiny or R 101. There are books and online resources to familiarize yourself with the content here. I will focus on the process of building the end-to-end project. As Hadley Wickham suggested in the book, the normal development of the Shiny app involves multiple iterations.
First Iteration: UI Layout And Server
Building a Shiny App usually includes two main components: the UI and the server. UI handles the displaying on the screen, and the server runs the backend logic to get it ready to dispatch to UI for rendering.
We can reuse the data we have to persist from the previous post and read them from the CSV file: R for Data Analysis: How to Find the Perfect Cocomelon Video for Your Kids.
Here is the complete code for the first iteration.
library(shiny)
library(DT)
library(dplyr)
library(readr)
library(ggplot2)
ui <- fluidPage(titlePanel(
h1(
"How R and Shiny Can Help You Find the Best Cocomelon Videos for Your Kids",
align = "center"
)
),
sidebarLayout(
sidebarPanel(
textInput(
inputId = 'number_records_display',
label = 'Number of records to Display',
value = '10'
),
width = 3
),
mainPanel(
h1("Explore Cocomelon Youtube Videos", align = "center"),
strong("The following charts display the scatter plot by views and likes"),
plotOutput("plot"),
tableOutput("video_table")
)
))
server <- function(input, output) {
data <-
reactive(
read_csv("~/Downloads/cocomelon_2023_3_2.csv") %>%
transform(
statistics.viewCount = as.numeric(statistics.viewCount),
statistics.likeCount = as.numeric(statistics.likeCount),
statistics.favoriteCount = as.numeric(statistics.favoriteCount),
snippet.publishedAt = as.Date(snippet.publishedAt)
) %>% mutate(
views = statistics.viewCount / 1000000,
likes = statistics.likeCount / 10000,
number_days_since_publish = as.numeric(Sys.Date() - snippet.publishedAt)
) %>% arrange(desc(statistics.viewCount))
)
output$plot <- renderPlot({
videos <-
data() %>% head(input$number_records_display)
ggplot(videos, aes(statistics.viewCount, statistics.likeCount)) +
geom_point()
}, res = 96)
output$video_table <- renderTable(
data() %>% select(
snippet.title,
statistics.viewCount,
statistics.likeCount,
contentDetails.duration,
number_days_since_publish
) %>% head(input$number_records_display)
)
}
shinyApp(ui = ui, server = server)
The first version of our application is straightforward:
For the UI part, we used sidebarLayout
in Shiny to split the sidebarPanel
and mainpanel
. In the sidebarPanel
we add an input text box to get the number of rows to display, which will further pass down the value to the server part and perform the head
operation. In the mainpanel
, we add a plotOutput
and a tableOutput
as a placeholder to display the data.
For the server part, we’d perform reading the data first. Then using the data transformed, we find all the output is defined in UI, supply the content for renderPlot
and renderTable
.
We call reactive
to reduce redundant operations and improve performance to give a better user experience. Shiny’s reactive expressions accomplish this. Reactive programming is declarative, which the engine can lazily evaluate and optimize. In our example, when the app initially loads, it reads the CSV file only once and caches the intermedia result. If the user changes the input from the default ten rows to 20 rows, we only go back to the earliest stage needed to rerun thehead
operation instead of reading from the CSV file again.
Here is the result for the first iteration

Second Iteration: Interactivity with User Click
Having an input text reflect changes in the plot and table is an exciting first step. You probably noticed that the plot and table quickly changed when I typed the number 2
. It’s efficient due to the reactive programming.
Shiny supports additional engagement on the plotOutput
which takes the users’ pointer event that feeds as input for the other outputs. In the previous step, there are many dots on the plot, but we need to know what a point on the plot represents. As a user, it can be helpful to provide users with additional information so the user can explore themselves.
Youtube API also provides the embedded iframe we can add to our web application. We need to search the title on Youtube to provide a seamless experience for our kids.
We’d need to add a few changes to our code to see this change. Here is the complete code for the second iteration.
library(shiny)
library(DT)
library(dplyr)
library(readr)
library(ggplot2)
ui <- fluidPage(titlePanel(
h1(
"How R and Shiny Can Help You Find the Best Cocomelon Videos for Your Kids",
align = "center"
)
),
sidebarLayout(
sidebarPanel(
textInput(
inputId = 'number_records_display',
label = 'Number of records to Display',
value = '10'
),
uiOutput("iframe"),
width = 4
),
mainPanel(
h1("Explore Cocomelon Youtube Videos", align = "center"),
strong("The following charts display the scatter plot by views and likes"),
textOutput("video_title"),
plotOutput("plot", click = "plot_click"),
verbatimTextOutput("clicked_video"),
DT::dataTableOutput("video_table")
)
))
server <- function(input, output) {
data <-
reactive(
read_csv("~/Downloads/cocomelon_2023_3_2.csv") %>%
transform(
statistics.viewCount = as.numeric(statistics.viewCount),
statistics.likeCount = as.numeric(statistics.likeCount),
statistics.favoriteCount = as.numeric(statistics.favoriteCount),
snippet.publishedAt = as.Date(snippet.publishedAt)
) %>% mutate(
views = statistics.viewCount / 1000000,
likes = statistics.likeCount / 10000,
number_days_since_publish = as.numeric(Sys.Date() - snippet.publishedAt)
) %>% select(
snippet.title,
statistics.viewCount,
statistics.likeCount,
contentDetails.duration,
number_days_since_publish,
player.embedHtml
) %>% arrange(desc(statistics.viewCount))
)
output$plot <- renderPlot({
videos <-
data() %>% head(input$number_records_display)
ggplot(videos, aes(statistics.viewCount, statistics.likeCount)) +
geom_point()
}, res = 96)
output$video_table = DT::renderDataTable({
req(input$plot_click)
top_videos <- data() %>% select(
snippet.title,
statistics.viewCount,
statistics.likeCount,
contentDetails.duration,
number_days_since_publish
) %>% head(input$number_records_display)
near_point <- nearPoints(top_videos,input$plot_click)
datatable(top_videos) %>%
formatStyle(
columns = 'statistics.viewCount',
target = 'row',
backgroundColor = styleEqual(near_point$statistics.viewCount, c('yellow'))
)
})
output$iframe <- renderUI({
near_point <- nearPoints(data() %>% head(input$number_records_display) ,input$plot_click)
HTML(near_point$player.embedHtml)
})
}
shinyApp(ui = ui, server = server)
On plotOutput
, we can define an id: plot_click. Whenever a position is clicked within the plot, The plot will send coordinates to the server and can be found in input$plot_click
.
We imported DT in our Shiny app this time. DT is an R interface to the JavaScript library DataTables. DT::renderDataTable
enables more robust functionality than Shiny’s default renderTable
. Once we can identify the pointer location to know where the user clicks, we can further highlight that specific row to the user. formatStyle
is one way that we can update the CSS attribute of the table. We used styleEqual
, whenever the clicked value matches, the defined style will be applied.
We also tried to get the closest point near_point <- nearPoints(data(),input$plot_click)
first. We cannot expect the users to click precisely on the exact point in the plot. nearPoints
is a helper function to get to the nearest point. Thus, it would highlight the clicked item correctly.
To display the video, we can leveragerenderUI
and use the iframe already provided by Youtube API on the sidebar.

What if users want to choose a list of videos to watch? To select multiple points, we can use brushedPoints
, all we’d need to do is change plotOutput(“plot”, click = “plot_click”)
to plotOutput(“plot”, brush = “plot_brush”)
, and we can collect the list of points by following
near_points <- brushedPoints(top_videos,input$plot_brush)
And we can choose multiple points now

Third Iteration: Add Some Theme
The theme is critical in making your web application look more polished. Shiny integrates well with bslib, a package that supports Bootstrap themes in R.
To enable a theme, we can add a line of code indicating which theme we are interested in fluidPage
theme = bs_theme(bootswatch = "sketchy")

Now, it’s time to publish our application and make it available to the public. There are multiple options mentioned on the Shiny tutorial page. RStudio makes it easy to follow the instructions here to publish your application.

Now here is our Shiny web application for the top videos of Cocomelon for our little ones:
Final Thoughts
In this story, I have extended my previous story R for Data Analysis: How to Find the Perfect Cocomelon Video for Your Kids, to be more engaging with Shiny. I have shared the multiple iterations on how to bring data analytics to the next stage: for storytelling and more helpful to our users (our kids 😊)
I also publish the web application for people who want to play with this interactive web page. I hope this article helps bring the ideas of data analytics to more people and lets more people learn about R and Shiny.
About Me
I hope my stories are helpful to you.
For data engineering post, you can also subscribe to my new articles or becomes a referred Medium member that also gets full access to stories on Medium.
More Articles

How to Visualize Monthly Expenses in a Comprehensive Way: Develop a Sankey Diagram in R
Personal budgeting APP like Mint/Personal Capital/Clarity only provide three limited types of charts. Have you ever wondered if charts are good enough to get better ideas on your monthly income and expense? Are there ways to visualize monthly expenses in a comprehensive way? In this article, I will share with you how to create a Sankey Diagram In R to better help you gain more insights into your personal financial situation.

Why R for Data Engineering is More Powerful Than You Thought
R could add potential benefits to help the data engineering community. Let’s discuss about Why R for Data Engineering is More Powerful Than You Thought.

Visualizing Data with ggridges: Techniques to Eliminate Density Plot Overlaps in ggplot2
When it comes to visualizing data with a histogram and dealing with multiple groups, it can be quite challenging. I have recently come across a useful ggplot2 extension called ggridges that has been helpful for my data exploratory tasks.