Shiny for Python Developers
If you already build interactive apps with Python frameworks like Streamlit or Gradio, you have a huge advantage when learning Shiny for R. The mental model is similar, but the syntax and architecture differ in important ways. This tutorial maps your existing Python knowledge to Shiny concepts, so you can start building right away.
The Mental Model: What Carries Over
As a Python developer, you’re used to defining UI and server logic, handling user inputs, and automatically updating outputs when values change. Shiny follows the same pattern, but with R syntax.
What stays the same:
- You define inputs (sliders, text boxes, dropdowns) and outputs (plots, tables, text)
- Changing an input automatically updates dependent outputs
- You run the app and view it in a browser
What changes:
- The language is R, not Python
- The architecture separates UI and server more explicitly
- Reactivity uses a different mechanism than Streamlit’s
@st.fragmentor Gradio’sfn
Your First Shiny App
Here’s a simple Shiny app that displays a histogram based on user input:
library(shiny)
library(ggplot2)
ui <- fluidPage(
titlePanel("Histogram Builder"),
sidebarLayout(
sidebarPanel(
sliderInput("bins", "Number of bins:",
min = 5, max = 50, value = 20)
),
mainPanel(
plotOutput("histogram")
)
)
)
server <- function(input, output, session) {
output$histogram <- renderPlot({
ggplot(faithful, aes(x = waiting)) +
geom_histogram(bins = input$bins, fill = "steelblue") +
labs(x = "Waiting time (minutes)", y = "Count")
})
}
shinyApp(ui = ui, server = server)
Output: A browser window opens showing a histogram with an interactive slider.
Compare this to Streamlit:
import streamlit as st
import pandas as pd
import ggplot
bins = st.slider("Number of bins", 5, 50, 20)
st.histogram(faithful, column="waiting", bins=bins)
The key difference: Streamlit uses decorators and executes top-to-bottom on each interaction. Shiny uses a reactive graph where you explicitly declare dependencies.
Inputs and Outputs: Streamlit vs Shiny
Here’s a side-by-side comparison of common input widgets:
| Python (Streamlit) | R (Shiny) |
|---|---|
st.slider("Label", min, max, default) | sliderInput("id", "Label", min, max, default) |
st.text_input("Label", "default") | textInput("id", "Label", value = "default") |
st.selectbox("Label", options) | selectInput("id", "Label", choices = options) |
st.checkbox("Label") | checkboxInput("id", "Label") |
In Shiny, every input gets an ID (like "bins"). You access it in the server as input$bins. Outputs also get IDs, and you render them with render* functions:
output$plot <- renderPlot({ ... })
output$table <- renderTable({ ... })
output$text <- renderText({ ... })
Reactivity: The Critical Difference
Streamlit reruns your entire script (or decorated function) when widgets change. Shiny’s reactivity is more granular and efficient.
In Shiny, reactive expressions only recalculate when their dependencies change:
server <- function(input, output, session) {
# This only runs when input$n changes
filtered_data <- reactive({
iris[1:input$n, ]
})
# This runs when filtered_data() changes
output$summary <- renderPrint({
summary(filtered_data())
})
# This also runs when filtered_data() changes
output$table <- renderTable({
filtered_data()
})
}
Notice the parentheses: filtered_data() is a function call. In Shiny, reactive expressions are functions—you must call them with () to get the value.
This is different from Gradio, where your function receives inputs directly:
def predict(text):
return model(text)
In Shiny, you access inputs via the input object and wrap outputs in render* functions.
Going Further
This tutorial covered the fundamentals. To build production-quality apps, explore these topics:
- Getting Started with Shiny — deeper dive into app structure
- Reactivity in Shiny — master reactive expressions and timing
- UI Components — buttons, tabsets, and layout options
- Deployment — share your app with the world
Key Takeaways
As a Python developer, you already understand the mental model. The main adjustments are:
- R syntax over Python — same patterns, different language
- Explicit UI/server separation — your UI and server are distinct objects
- Reactive graph, not top-down execution — outputs depend on specific inputs, not the whole script
- Call reactive expressions as functions — use
()to access their values
Once these click, you’ll find Shiny’s reactive system actually gives you more control than Streamlit’s automatic reruns. Happy building!