Join the R Community at ShinyConf 2023

R Shiny vs Shiny for Python (PyShiny) blog hero banner

R Shiny vs Shiny for Python: What are the Key Differences


If you haven’t been living under a rock for the past couple of weeks, you’ve likely noticed some groundbreaking news in the Shiny department. Yes, it’s finally available for Python! But how is the current Shiny for Python version? How does R Shiny compare vs Shiny for Python (PyShiny)? We have the answers, so continue reading.

Shiny for Python is still a work in progress, but you can already build some great PyShiny demos!

Today we’ll go over a detailed R Shiny vs. Shiny for Python comparison in 4 key areas – startup code, UI elements, server code/reactivity, and dashboard styling. Each key area will demonstrate code and end-result differences for both programming languages.

At the time of writing this post, Shiny for Python is still in alpha. The Shiny for Python ecosystem is quite limited at the moment, so if you’re looking for enterprise-grade Shiny apps, skip this article and go straight to R Shiny for your enterprise needs. But with that being said, there’s still quite a lot you can do with PyShiny – let’s find out below.

Table of contents:


Boilerplate Code Needed to Run the App

So, what is a boilerplate code? Put simply, it’s the code that every app/dashboard has in common. In Shiny, it usually boils down to library imports, UI and server declaration, and their connection in a Shiny app.

Python and R have different views on best programming practices. In R, you import a package and have all the methods available instantly. In Python, you usually import required modules of a library and then call the methods with <module-name>.<method-name> syntax.

Want a Shiny app fast? Try Appsilon’s Shiny templates and have an R Shiny dashboard in less than 10 minutes.

To be fair, you can make Python work like R by using the from <module-name> import * syntax, but it’s not recommended way. Avoid this approach at all costs.

Let’s now take a look at the amount of code needed to write the most basic R Shiny app that renders one heading element:

library(shiny)


ui <- fluidPage(
  tags$h2("My R Shiny Dashboard")
)

server <- function(input, output, session) { }

shinyApp(ui = ui, server = server)

For reference, here’s what it looks like:

Image 1 - Boilerplate R Shiny app

Image 1 – Boilerplate R Shiny app

And here’s the same Shiny app in Python:

from shiny import App, ui


app_ui = ui.page_fluid(
    ui.tags.h2("My Python Shiny Dashboard")
)

def server(input, output, session): 
    pass


app = App(ui=app_ui, server=server)

It looks more or less identical:

Image 2 - Boilerplate Shiny for Python app

Image 2 – Boilerplate Shiny for Python app

So, what’s the difference? The difference boils down to recommended programming practices of individual languages. Both do the same thing in different ways. Both are great, but neither is better. It’s just preference.

We’ll now take a look at how you can declare UI elements in R Shiny / Shiny for Python.

UI Elements – R vs. Python

The goal of this section is to create a form-based Shiny application in both R Shiny and Shiny for Python. We won’t handle the user input but only deal with the UI instead.

Want to monitor R Shiny user sessions and draw them as a heatmap? Our detailed guide to R shinyheatmap package has you covered.

Let’s start with R Shiny. It’s incredibly easy to add input controls with methods such as selectInput(), radioButtons() and sliderInput(). Here’s the entire code snippet:

library(shiny)

ui <- fluidPage(
  tags$h1("Heading 1"),
  selectInput(
    inputId = "selectLevel",
    label = "Filter by level:",
    choices = c("Junior", "Mid level", "Senior"),
    selected = c("Junior")
  ),
  selectInput(
    inputId = "selectSkills",
    label = "Filter by skills:",
    choices = c("Python", "R", "Machine learning"),
    selected = c("Python", "Machine learning"),
    multiple = TRUE
  ),
  radioButtons(
    inputId = "radioExperience",
    label = "Experience level:",
    choices = c("0-1 years of experience", "2-5 years of experience", "5+ years of experience"),
    selected = c("2-5 years of experience")
  ),
  checkboxGroupInput(
    inputId = "cbxAdditional",
    label = "Additional:",
    choices = c("Married", "Has kids"),
    selected = c("Married")
  ),
  sliderInput(
    inputId = "slider",
    label = "Overall impression:",
    value = 5,
    min = 1,
    max = 10
  ),
  textInput(
    inputId = "textAdditional",
    label = "Anything to add?"
  )
)

server <- function(input, output) { }

shinyApp(ui, server)

The corresponding Shiny app looks like this:

Image 3 - R Shiny app with UI elements

Image 3 – R Shiny app with UI elements

The story is similar in Python. All UI elements have to be imported from shiny.ui, and you can then access individual UI elements by calling ui.<input-name>().

Here’s the code snippet which creates the same application as in R Shiny:

from shiny import App, ui


app_ui = ui.page_fluid(
    ui.tags.h1("Heading 1"),
    ui.input_select(
        id="selectLevel",
        label="Filter by level:",
        choices=["Junior", "Mid level", "Senior"],
        selected="Junior"
    ),
    ui.input_select(
        id="selectSkills",
        label="Filter by skills:",
        choices=["Python", "R", "Machine learning"],
        selected=["Python", "Machine learning"],
        multiple=True
    ),
    ui.input_radio_buttons(
        id="radioExperience",
        label="Experience level:",
        choices=["0-1 years of experience", "2-5 years of experience", "5+ years of experience"],
        selected="2-5 years of experience"
    ),
    ui.input_checkbox_group(
        id="cbxAdditional",
        label="Additional:",
        choices=["Married", "Has kids"],
        selected="Married"
    ),
    ui.input_slider(
        id="slider",
        label="Overall impression:",
        value=5,
        min=1,
        max=5
    ),
    ui.input_text(
        id="textAdditional",
        label="Anything to add?"
    )
)

def server(input, output, session): 
    pass


app = App(ui=app_ui, server=server)

Let’s see what it looks like:

Image 4 - Python for Shiny app with UI elements

Image 4 – Python for Shiny app with UI elements

The UI elements are ever so slightly different between the languages. Other than that, Python’s naming convention is much more consistent. For example, all input elements start with the input_ keyword. That’s not the case in R, where the same keyword is put at the end of the function name.

As before, it’s just personal preference, but it might be faster for beginners to create user inputs in Python, as code completion will work much better.

Server Logic – Is R Shiny Better than Shiny for Python?

An application without interactive components isn’t an application – it’s just a collection of UI elements. It’s essential for any Shiny developer to learn how to make their apps reactive by working with data, rendering text, plots, tables, and so on.

In this section, we’ll show you how to draw a histogram of 200 data points drawn from a Normal distribution with a mean of 100, and a standard deviation of 15. The number of histogram bins is controlled by the user via a slider.

But what is a Histogram? Here’a complete guide to Histograms in R and ggplot2.

Long story short, this is the code you need to make an interactive R Shiny dashboard:

library(shiny)
library(ggplot2)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "n", label = "N", min = 0, max = 100, value = 20)
    ),
    mainPanel(
      textOutput(outputId = "text_n"),
      plotOutput(outputId = "histogram")
    )
  )
)

server <- function(input, output, session) {
  output$text_n <- renderText({ 
    paste0("Number of bins: ", input$n)
  })
  
  output$histogram <- renderPlot({
    df <- data.frame(x = (100 + 15 * rnorm(200)))
    ggplot(df, aes(x)) + 
      geom_histogram(color = "#000000", fill = "#0099F8", bins = input$n)
  })
}

shinyApp(ui = ui, server = server)

Let’s see what it looks like:

Image 5 - R Shiny dashboard rendering a histogram

Image 5 – R Shiny dashboard rendering a histogram

The procedure is quite simple. You have to call a corresponding rendering function – e.g., renderText() to render text, or renderPlot() to show a chart, and then write an expression inside it. For ggplot2, it’s best to have the data in form of a data frame.

Let’s see how Python compares. We’re using the matplotlib library to render charts, and the entire UI portion is pretty much identical to R.

The difference becomes visible in the server portion. Python uses function decorators to render elements. Here are some rules you’ll have to remember:

  • Use @output decorator any time you want to control what’s rendered in a single element.
  • Use @render.<element-name> decorator to denote which element type you will render.
  • Add a function call below – the function has to be called identically to an ID of the output UI element, so keep that in mind.

That’s the main difference between Python to R, and you’ll quickly get the hang of it. Other than that, you can put any Python code inside the function and return what you want to display.

Here’s the entire code snippet for the app:

from shiny import App, render, ui
import numpy as np
import matplotlib.pyplot as plt


app_ui = ui.page_fluid(
    ui.layout_sidebar(
        ui.panel_sidebar(
            ui.input_slider(id="n", label="N", min=0, max=100, value=20)
        ),
        ui.panel_main(
            ui.output_text(id="text_n"),
            ui.output_plot(id="histogram")
        )
    )
)

def server(input, output, session):
    @output
    @render.text
    def text_n():
        return f"Number of bins: {input.n()}"

    @output
    @render.plot
    def histogram():
        x = 100 + 15 * np.random.randn(200)
        fig, ax = plt.subplots()
        ax.hist(x, bins=input.n(), ec="#000000", color="#0099F8")
        return fig


app = App(ui=app_ui, server=server)

It should look almost identical to the R Shiny version:

Image 6 - Shiny for Python dashboard rendering a histogram

Image 6 – Shiny for Python dashboard rendering a histogram

And it does – with marginal differences between matplotlib and ggplot2, but we won’t get into these.

As the last area of this R Shiny vs. Shiny for Python comparison, we’ll take a look at stylings with CSS.

Styling Shiny Dashboards – Which Language Does it Better?

R Shiny has been around the block for longer, hence it currently has more ways (at least documented ones) to add custom CSS to your dashboards. This doesn’t mean you can’t add CSS to Shiny for Python apps, but you’re a bit limited at this point in time.

For R Shiny, we’ll create a www folder and create the following main.css file inside it:

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Poppins', sans-serif;
    font-size: 2rem;
    background-color: #EEEEEE;
}

.container-fluid > .row {
    display: flex;
    flex-direction: column;
    align-items: center;
}

The CSS file resets the padding/margins, changes the default font, and centers every element. To change the theme of the R Shiny app, simply specify the theme parameter in the fluidPage() method:

ui <- fluidPage(
  theme = "main.css",
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "n", label = "N", min = 0, max = 100, value = 20)
    ),
    mainPanel(
      textOutput(outputId = "text_n"),
      plotOutput(outputId = "histogram")
    )
  )
)

The styled R Shiny app looks like this:

Image 7 - Styled R Shiny app

Image 7 – Styled R Shiny app

It’s a bit different story with Shiny for Python. As of now, there’s no theme parameter for page_fluid() function, nor does adding a link to the file work.

The only currently viable option is to add CSS styles directly to the app UI:

app_ui = ui.page_fluid(
    ui.tags.head(
        ui.tags.style("""
            @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');

            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                font-family: 'Poppins', sans-serif;
                font-size: 2rem;
                background-color: #EEEEEE;
            }

            .container-fluid > .row {
                display: flex;
                flex-direction: column;
                align-items: center;
            }
        """)
    ),
    ui.layout_sidebar(
        ui.panel_sidebar(
            ui.input_slider(id="n", label="N", min=0, max=100, value=20)
        ),
        ui.panel_main(
            ui.output_text(id="text_n"),
            ui.output_plot(id="histogram")
        )
    ),
)

Here’s the corresponding app:

Image 8 - Styled Shiny for Python app

Image 8 – Styled Shiny for Python app

It’s not the cleanest solution, especially because CSS files tend to have thousands of lines, but it’s something we’ll have to deal with in these alpha releases.


Summary of R Shiny vs. Shiny for Python

And that does it for our R Shiny vs. Shiny for Python comparison. We’ve compared the two in four areas, and can conclude they’re pretty much identical, except for the following:

  • Shiny for Python packs a much more consistent naming convention for specifying inputs.
  • R Shiny is currently easier to style with CSS.
  • Server/reactive functionality involves a bit more code in Python due to function decorators.

Other than that, the two are nearly identical, at least in this alpha release of Shiny for Python.

What are your thoughts on Shiny for Python? Have you encountered any other shortcomings when compared to R? Please let us know in the comment section below.

Considering a career as a Shiny Developer? Appsilon’s complete career guide for R Shiny has you covered.