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

By:
Dario Radečić
October 6, 2022

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 <a href="https://appsilon.com/shiny-for-python-introduction/" target="_blank" rel="noopener">available for Python</a>! 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. <blockquote>Shiny for Python is still a work in progress, but you can already build some <a href="https://appsilon.com/pyshiny-demo/" target="_blank" rel="noopener">great PyShiny demos</a>!</blockquote> 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 <a href="https://appsilon.com/why-you-should-use-r-shiny-for-enterprise-application-development/" target="_blank" rel="noopener">R Shiny for your enterprise needs</a>. But with that being said, there's still quite a lot you can do with PyShiny - let's find out below. Table of contents: <ul><li><a href="#boilerplate">Boilerplate Code Needed to Run the App</a></li><li><a href="#ui">UI Elements - R vs. Python</a></li><li><a href="#server">Server Logic - Is R Shiny Better than Shiny for Python?</a></li><li><a href="#styling">Styling Shiny Dashboards - Which Language Does it Better?</a></li><li><a href="#summary">Summary of R Shiny vs. Shiny for Python</a></li></ul> <hr /> <h2 id="boilerplate">Boilerplate Code Needed to Run the App</h2> So, what is a <i>boilerplate code</i>? 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 <code>&lt;module-name&gt;.&lt;method-name&gt;</code> syntax. <blockquote>Want a Shiny app fast? Try Appsilon's Shiny templates and have an <a href="https://appsilon.com/r-shiny-dashboard-templates/" target="_blank" rel="noopener">R Shiny dashboard in less than 10 minutes</a>.</blockquote> To be fair, you can make Python work like R by using the <code>from &lt;module-name&gt; import *</code> 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: <pre><code class="language-r">library(shiny) <br> ui &lt;- fluidPage(  tags$h2("My R Shiny Dashboard") ) <br>server &lt;- function(input, output, session) { } <br>shinyApp(ui = ui, server = server)</code></pre> For reference, here's what it looks like: <img class="size-full wp-image-15056" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d33fa11449a24ce96ef8_6e20689c_1-1.webp" alt="Image 1 - Boilerplate R Shiny app" width="2044" height="1722" /> Image 1 - Boilerplate R Shiny app And here's the same Shiny app in Python: <pre><code class="language-python">from shiny import App, ui <br> app_ui = ui.page_fluid(    ui.tags.h2("My Python Shiny Dashboard") ) <br>def server(input, output, session):    pass <br> app = App(ui=app_ui, server=server)</code></pre> It looks more or less identical: <img class="size-full wp-image-15058" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d340842ddef80cb6ea86_815ce386_2-1.webp" alt="Image 2 - Boilerplate Shiny for Python app" width="2134" height="1714" /> Image 2 - Boilerplate Shiny for Python app <b>So, what's the difference?</b> 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. <h2 id="ui">UI Elements - R vs. Python</h2> 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. <blockquote>Want to monitor R Shiny user sessions and draw them as a heatmap? <a href="https://appsilon.com/r-shinyheatmap/" target="_blank" rel="noopener">Our detailed guide to R shinyheatmap package has you covered</a>.</blockquote> Let's start with R Shiny. It's incredibly easy to add input controls with methods such as <code>selectInput()</code>, <code>radioButtons()</code> and <code>sliderInput()</code>. Here's the entire code snippet: <pre><code class="language-r">library(shiny) <br>ui &lt;- 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?"  ) ) <br>server &lt;- function(input, output) { } <br>shinyApp(ui, server)</code></pre> The corresponding Shiny app looks like this: <img class="size-full wp-image-15060" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d34193029c937a7f6091_da6169df_3-1.webp" alt="Image 3 - R Shiny app with UI elements" width="1894" height="1784" /> Image 3 - R Shiny app with UI elements The story is similar in Python. All UI elements have to be imported from <code>shiny.ui</code>, and you can then access individual UI elements by calling <code>ui.&lt;input-name&gt;()</code>. Here's the code snippet which creates the same application as in R Shiny: <pre><code class="language-python">from shiny import App, ui <br> 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?"    ) ) <br>def server(input, output, session):    pass <br> app = App(ui=app_ui, server=server)</code></pre> Let's see what it looks like: <img class="size-full wp-image-15062" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d34200fd9b7af132f42c_7103021c_4.webp" alt="Image 4 - Python for Shiny app with UI elements" width="2272" height="2138" /> 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 <code>input_</code> 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. <h2 id="server">Server Logic - Is R Shiny Better than Shiny for Python?</h2> 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 <b>histogram bins</b> is controlled by the user via a slider. <blockquote>But what is a Histogram? <a href="https://appsilon.com/ggplot2-histograms/" target="_blank" rel="noopener">Here'a complete guide to Histograms in R and ggplot2</a>.</blockquote> Long story short, this is the code you need to make an interactive R Shiny dashboard: <pre><code class="language-r">library(shiny) library(ggplot2) <br>ui &lt;- fluidPage(  sidebarLayout(    sidebarPanel(      sliderInput(inputId = "n", label = "N", min = 0, max = 100, value = 20)    ),    mainPanel(      textOutput(outputId = "text_n"),      plotOutput(outputId = "histogram")    )  ) ) <br>server &lt;- function(input, output, session) {  output$text_n &lt;- renderText({    paste0("Number of bins: ", input$n)  })    output$histogram &lt;- renderPlot({    df &lt;- data.frame(x = (100 + 15 * rnorm(200)))    ggplot(df, aes(x)) +      geom_histogram(color = "#000000", fill = "#0099F8", bins = input$n)  }) } <br>shinyApp(ui = ui, server = server)</code></pre> Let's see what it looks like: <img class="size-full wp-image-15064" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d342bd0d518f384a9f6e_a61d0596_5.webp" alt="Image 5 - R Shiny dashboard rendering a histogram" width="2044" height="1572" /> Image 5 - R Shiny dashboard rendering a histogram The procedure is quite simple. You have to call a corresponding rendering function - e.g., <code>renderText()</code> to render text, or <code>renderPlot()</code> to show a chart, and then write an expression inside it. For <code>ggplot2</code>, it's best to have the data in form of a data frame. Let's see how Python compares. We're using the <code>matplotlib</code> 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: <ul><li>Use <code>@output</code> decorator any time you want to control what's rendered in a single element.</li><li>Use <code>@render.&lt;element-name&gt;</code> decorator to denote which element type you will render.</li><li>Add a function call below - the function has to be called identically to an <b>ID of the output UI element</b>, so keep that in mind.</li></ul> 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: <pre><code class="language-python">from shiny import App, render, ui import numpy as np import matplotlib.pyplot as plt <br> 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")        )    ) ) <br>def server(input, output, session):    @output    @render.text    def text_n():        return f"Number of bins: {input.n()}" <br>    @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 <br> app = App(ui=app_ui, server=server)</code></pre> It should look almost identical to the R Shiny version: <img class="size-full wp-image-15066" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d343d16df45f371eba57_5641b79b_6.webp" alt="Image 6 - Shiny for Python dashboard rendering a histogram" width="2272" height="1714" /> Image 6 - Shiny for Python dashboard rendering a histogram And it does - with marginal differences between <code>matplotlib</code> and <code>ggplot2</code>, 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. <h2 id="styling">Styling Shiny Dashboards - Which Language Does it Better?</h2> 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 <code>www</code> folder and create the following <code>main.css</code> file inside it: <pre><code class="language-css">@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&amp;display=swap'); <br>* {    margin: 0;    padding: 0;    box-sizing: border-box;    font-family: 'Poppins', sans-serif;    font-size: 2rem;    background-color: #EEEEEE; } <br>.container-fluid &gt; .row {    display: flex;    flex-direction: column;    align-items: center; }</code></pre> 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 <code>theme</code> parameter in the <code>fluidPage()</code> method: <pre><code class="language-r">ui &lt;- 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")    )  ) )</code></pre> The styled R Shiny app looks like this: <img class="size-full wp-image-15068" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d343b6953e1bff258dc4_173f73ac_7.webp" alt="Image 7 - Styled R Shiny app" width="2018" height="1830" /> Image 7 - Styled R Shiny app It's a bit different story with Shiny for Python. As of now, there's no <code>theme</code> parameter for <code>page_fluid()</code> 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: <pre><code class="language-python">app_ui = ui.page_fluid(    ui.tags.head(        ui.tags.style("""            @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&amp;display=swap'); <br>            * {                margin: 0;                padding: 0;                box-sizing: border-box;                font-family: 'Poppins', sans-serif;                font-size: 2rem;                background-color: #EEEEEE;            } <br>            .container-fluid &gt; .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")        )    ), )</code></pre> Here's the corresponding app: <img class="size-full wp-image-15070" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d34493029c937a7f621d_ee68ac01_8.webp" alt="Image 8 - Styled Shiny for Python app" width="2236" height="1714" /> 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. <hr /> <h2 id="summary">Summary of R Shiny vs. Shiny for Python</h2> 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: <ul><li>Shiny for Python packs a much more consistent naming convention for specifying inputs.</li><li>R Shiny is currently easier to style with CSS.</li><li>Server/reactive functionality involves a bit more code in Python due to function decorators.</li></ul> Other than that, the two are nearly identical, at least in this alpha release of Shiny for Python. <i>What are your thoughts on Shiny for Python? Have you encountered any other shortcomings when compared to R?</i> Please let us know in the comment section below. <blockquote>Considering a career as a Shiny Developer? <a href="https://appsilon.com/how-to-start-a-career-as-an-r-shiny-developer/" target="_blank" rel="noopener">Appsilon's complete career guide for R Shiny has you covered</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
python
shiny dashboards
shiny for python