top of page

From Streamlit to Shiny: Visualizing My Goodreads Reading Journey

Photo by Kenny Eliason on Unsplash
Photo by Kenny Eliason on Unsplash

In 2023, I was bored and started a project to understand my reading habits better. I used Streamlit to create a simple app that visualized my Goodreads data.

As someone who loves books and enjoys working with data, this was a fun way to explore trends in my reading journey. I wanted to see my favorite books and how my reading changed over time.

In 2024, over the Christmas holiday again, I decided to take things further by upgrading the app to a Shiny application.

This process taught me so much about building apps and creating better visualizations and, of course, prompting Claude because I did this over the Christmas holiday and wanted to go back to having fun :).

I’m excited to share how it all came together. 😊

Building a Basic App with Streamlit in 2023

The first version of the app was built using Python and Streamlit.

Streamlit made it easy to pull data from Goodreads and create something functional.


The app displayed things like:

  • Popular authors in my library

  • Monthly and yearly reading trends

  • A section highlighting my top-rated reads

I used libraries like pandas, matplotlib, and plotly to process and visualize the data, and of course, I got some help from ChatGPT at the time (don’t judge me, it was Christmas still).

While the app worked well and gave me some great insights, it also had its limits:

  • The visualizations weren’t very interactive

  • There wasn’t much room for customization

  • The interface felt a little basic and less polished than I had hoped for

Even with these challenges, the project was fun to do.

How the Streamlit App Worked

The Streamlit app processed Goodreads data to generate key insights.

Here are the main features:

  1. Book Ratings: Displayed a histogram of book ratings, with options to filter by year.

  2. Most Common Authors: Highlighted the top ten most frequent authors in the dataset.

  3. Cumulative Books: Showed how the number of books added grew over time.

  4. Book Lengths: Visualized the distribution of page counts for books.

  5. Read vs. Unread Books: Provided a pie chart comparison of read and unread books.

  6. Word Cloud: Generated a word cloud of book titles to show reading diversity.

The app allowed users to upload their Goodreads data.

For example, the following code snippet shows how the app processes the data and creates visualizations:

# Function to load data
import pandas as pd

def load_data(uploaded_file):
    data = pd.read_csv(uploaded_file)
    data['Date Added'] = pd.to_datetime(data['Date Added'], errors='coerce')
    data['Date Read'] = pd.to_datetime(data['Date Read'], errors='coerce')
    return data

# Visualizing book ratings
def plot_book_ratings(data):
    import plotly.express as px
    fig = px.histogram(data, x='My Rating', title='Distribution of Book Ratings')
    return fig
To try it out, visit the Streamlit app here or check out the full code on GitHub.

Why Transition to Shiny in 2024?

When I revisited the project, I knew I wanted to make it better.

That’s when I turned to Shiny, an R-based framework for building interactive apps.

Shiny’s interactive features and flexibility were exactly what I needed to take the app to the next level. Also, Shiny integrates seamlessly with R’s data manipulation libraries, making it easier to manage and scale the app (if I decide to). Plus, it’s very easy to deploy Shiny apps, especially on shinyapps.io (of course, there are more deployment options).

Understanding the Shiny Code

The Shiny app code is structured into three key components: global.R, server.R, and ui.R.

Each plays a distinct role in creating a functional, interactive, and visually appealing app.

global.R — holds everything together

The global.R file is the backbone of the app. It loads all the necessary libraries and data, making them accessible across the app.

I used popular libraries like tidyverse for data wrangling, plotly for interactive visualizations, and wordcloud2 for a playful title cloud feature.

suppressPackageStartupMessages({
  library(shiny)
  library(tidyverse)
  library(plotly)
  library(wordcloud2)
  library(DT)
  library(lubridate)
  library(tidytext)
})

# Load example data
source("data/example_data.R")

By separating data and library loading from the UI and server, global.R keeps the app organized and easy to maintain.

Also, by suppressing package startup messages, the app avoids clutter in the console, and sourcing the example data separately keeps the code clean and organized.

server.R — where all the logic happens

The server.R file handles the app's logic.

It processes user inputs, updates visualizations dynamically, and creates summaries.

Let’s break it down:

  • The app uses reactive values to filter and update data based on user inputs.

filtered_data <- reactive({
  req(rv$data)
  data <- rv$data %>%
    mutate(
      Date_Added = as.Date(`Date Added`, format = "%Y/%m/%d"),
      Date_Read = as.Date(`Date Read`, format = "%Y/%m/%d")
    )

  if (input$year != "all") {
    data <- data %>% filter(year(Date_Added) == input$year)
  }
  data
})
  • Using plotly, the app generates dynamic plots, such as bar charts and cumulative progress lines.

output$cumulative_plot <- renderPlotly({
  req(filtered_data())

  plot_data <- filtered_data() %>%
    arrange(Date_Added) %>%
    mutate(cumulative = row_number())

  plot_ly(plot_data, x = ~Date_Added, y = ~cumulative, type = "scatter",
          mode = "lines", line = list(color = "#ffb6c1")) %>%
    layout(
      title = list(text = "Reading Journey", font = list(family = "Quicksand")),
      xaxis = list(title = "Date"),
      yaxis = list(title = "Number of Books"),
      paper_bgcolor = "rgba(0,0,0,0)",
      plot_bgcolor = "rgba(0,0,0,0)"
    )
})
  • The wordcloud2 package creates an engaging visualization of frequently occurring words in book titles.

output$title_cloud <- renderWordcloud2({
  req(filtered_data())
  
  words <- filtered_data()$Title %>%
    str_split(" ") %>%
    unlist() %>%
    table() %>%
    data.frame() %>%
    arrange(desc(Freq))
  
  wordcloud2(words, color = "#ffb6c1", backgroundColor = "#fff5f7")
})

ui.R — for designing the user interface

The ui.R file defines the app's layout and styling.

It features a clean, responsive design with a soft pink color palette, thanks to custom CSS.

body {
  background-color: #fff5f7;
  font-family: 'Quicksand', sans-serif;
}

.stats-box {
  background-color: #ffeef2;
  border-radius: 15px;
  text-align: center;
}

These styles, combined with the Quicksand font, create a warm interface.

The UI is divided into tabs, each serving a specific purpose:

  • Summary: A snapshot of total books, average rating, and pages read.

  • Ratings: A bar chart and stats on book ratings.

  • Progress: A timeline of books added.

  • Books: Interactive tables for exploring books and recent additions.

  • Authors: Charts and tables for the most-read authors.

  • Book Lengths: A histogram of page counts.

  • Title Cloud: A colorful word cloud of book titles.Key Features of the Shiny App

fluidPage(
  tags$head(
    tags$title("📚 Goodreads Wrapped"),
    tags$link(href = "https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap",
              rel = "stylesheet"),
    tags$link(rel = "stylesheet", type = "text/css", href = "custom.css")
  ),

  titlePanel(div(style = "text-align: center; padding: 20px;",
                 "✨ My Reading Journey ✨")),

  sidebarLayout(
    sidebarPanel(
      fileInput("file", "Upload your Goodreads CSV",
                accept = c(".csv")),
      
      selectInput("year", "Select Year:",
                  choices = c("All Time" = "all")),
      
      actionButton("load_example", "Try Example Data",
                   style = "background-color: #ffb6c1; color: white; width: 100%; border: none; border-radius: 20px;"),
      
      width = 3
    ),

    mainPanel(
      tabsetPanel(
        tabPanel("📚 Summary",
                 fluidRow(
                   column(4, div(class = "stats-box", textOutput("total_books"))),
                   column(4, div(class = "stats-box", textOutput("avg_rating"))),
                   column(4, div(class = "stats-box", textOutput("total_pages")))
                 )),
        
        tabPanel("⭐ Ratings", plotlyOutput("ratings_plot")),
        tabPanel("📈 Progress", plotlyOutput("cumulative_plot")),
        tabPanel("🌟 Title Cloud", wordcloud2Output("title_cloud"))
      )
    )
  )
)

Bringing It All Together

The app is brought to life by the interplay of these components.

Here’s how they work together:

  1. Global Initialization:


    All libraries and data are loaded in global.R, ensuring the app has what it needs right from the start.

  2. Dynamic Server Logic:


    The server.R file listens to user inputs and dynamically updates the interface with visualizations, tables, and statistics.

  3. Aesthetic and Functional UI:


    The ui.R file ties everything together with an intuitive layout and custom styling, making the app both functional and visually appealing.

  4. Custom CSS:


    The pink color palette and rounded edges makes the app look cute.

You can check out the updated Shiny app and explore the code on GitHub.

Here’s What I Learned

Both tools are great in their own way. Streamlit is great for quick prototypes, while Shiny shines when you need interactivity and customization and perhaps see the potential to further scale your dashboard/application.

What’s Next

While I’m happy with how the app turned out, I’m already thinking about the next steps. Here are some ideas I’m exploring:

  • Letting users share their reading journeys in a downloadable format or compare stats with friends.

  • Adding models to predict reading preferences and recommend books. (maybe?)

Conclusion

This was fun. The move from Streamlit to Shiny allowed me to create a cuter (I mean, it’s pink) and more interactive application.

If you’d like to check out the Shiny app, feel free to give it a try. I’d love to hear your thoughts or ideas for future features.

Happy New Year! 🎉

Comments


  • LinkedIn
  • Medium
  • GitHub
  • Twitter

Gigi Kenneth

Contact

Ask me anything

bottom of page