BI as Code logos

The Most Popular Code-based Business Intelligence Tools, Reviewed

Comparing the most popular BI-as-code tools: Evidence, Streamlit, Dash, Observable, Shiny and Quarto

Casey is a data scientist, software engineer and writer. She has previously worked at McKinsey & QuantumBlack, and is currently at Shopify.

There is no single ‘best’ business intelligence (BI) tool; the best tool for you depends on your specific needs, workflow, and skill set.

This guide compares some of the most popular BI-as-code tools to help you find the best fit for your data analytics stack and technical expertise:

Each of these tools are open source, and you can find the source code on GitHub.

Tool GitHub Repo License Languages Stars
Evidence evidence-dev/evidence MIT SQL/Markdown 4.3k
Streamlit streamlit/streamlit Apache 2.0 Python 35k
Dash plotly/dash MIT Python 21k
Observable observablehq/framework ISC JavaScript 2.5k
Shiny rstudio/shiny GPL-3.0 R/Python 5.4k
Quarto quarto-dev/quarto-cli MIT Markdown/Jupyter 3.9k

Evidence

Evidence logo

A SQL and Markdown tool for creating data apps

Evidence stands out for its management of data inputs through SQL queries and its creation of page content via Markdown and pre-built components.

Data inputs in Evidence are managed using SQL queries. Page content is created via Markdown and pre-built Evidence components, for common visualizations like tables or bar charts.

Evidence is designed for analysts familiar with SQL and Markdown, offering extensibility through web standards. Evidence apps are polished, performant, and easy for business stakeholders to consume.

Evidence also gives you unlimited ability to define your own custom components using HTML and JavaScript, as well as page styling via CSS. It also supports an ever-growing list of deployment options, including Evidence Cloud - a secure, managed hosting service.

Example Code

# Sales Report

<Slider min=2019 max=2024 name=year_pick title=Year size=full/>

```sql sales_by_month
SELECT
    order_month,
    category,
    sum(sales) AS sales
FROM orders
WHERE year = '${inputs.year_pick}'
GROUP BY ALL
```

<BarChart
  data={sales_by_month}
  title="Sales by Month"
  x=order_month
  y=sales
/>
Evidence screenshot

A good choice if:

Not recommended if:

Streamlit

Streamlit logo

A web app wrapper around pandas, numpy, and other Python data science staples

If you’re already familiar with things like numpy or pandas, Streamlit’s documentation will make you feel right at home. By wrapping, say, np.histogram in something like st.bar_chart, Streamlit takes care of translating your Python code into a web app.

Streamlit runs your Python script from top to bottom, streaming outputs like text, tables, or charts to the page. This tool can also be used to create a ChatGPT-style chatbot using Python-based outputs.

Example Code

import streamlit as st
import pandas as pd
import numpy as np

st.title('Uber pickups in NYC')

DATE_COLUMN = 'date/time'
DATA_URL = ('https://s3-us-west-2.amazonaws.com/'
         'streamlit-demo-data/uber-raw-data-sep14.csv.gz')

@st.cache_data
def load_data(nrows):
    data = pd.read_csv(DATA_URL, nrows=nrows)
    lowercase = lambda x: str(x).lower()
    data.rename(lowercase, axis='columns', inplace=True)
    data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN])
    return data

# Create a text element and let the reader know the data is loading.
data_load_state = st.text('Loading data...')
# Load 10,000 rows of data into the dataframe.
data = load_data(10000)
# Notify the reader that the data was successfully loaded.
data_load_state.text("Done! (using st.cache_data)")

st.subheader('Raw data')
st.write(data)

st.subheader('Number of pickups by hour')

hist_values = np.histogram(
    data[DATE_COLUMN].dt.hour, bins=24, range=(0,24))[0]

st.bar_chart(hist_values)

hour_to_filter = st.slider('hour', 0, 23, 17)  # min: 0h, max: 23h, default: 17h
filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter]
st.subheader(f'Map of all pickups at {hour_to_filter}:00')
st.map(filtered_data)
Streamlit screenshot

A good choice if:

Not recommended if:

Dash

Dash logo

A Python-native web app framework that gives direct control over layouts, DOM elements, and callbacks

Dash allows Python developers to create interactive web apps without needing to learn JavaScript. It offers substantial control and customization for those who are willing to dig into documentation. The core of Dash is a Python class that combines a few concepts:

Dash is built on top of Flask, so any Python developer who has experience using a web framework should feel comfortable in it. While R, Julia, and F# are also listed as compatible languages, the vast majority of Dash’s documentation is written for Python.

Dash is a powerful choice for experienced Python programmers seeking fine-grained control. If you’re not comfortable with mental models like classes, callbacks, or the DOM, however, the learning curve in Dash may seem a bit steep.

Example Code

from dash import Dash, html, dcc, callback, Output, Input
import plotly.express as px
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv')

app = Dash()

app.layout = [
    html.H1(children='Title of Dash App', style={'textAlign':'center'}),
    dcc.Dropdown(df.country.unique(), 'Canada', id='dropdown-selection'),
    dcc.Graph(id='graph-content')
]

@callback(
    Output('graph-content', 'figure'),
    Input('dropdown-selection', 'value')
)
def update_graph(value):
    dff = df[df.country==value]
    return px.line(dff, x='year', y='pop')

if __name__ == '__main__':
    app.run(debug=True)
Dash screenshot

A good choice if:

Not recommended if:

Observable Framework

Observable logo

A visualization toolkit for JavaScript-native web developers

If npm run dev is more your speed, Observable Framework is a great choice for bringing the full power of web development to your data visualization. By giving you importable helpers like Plot and FileAttachment, Observable makes it easy to integrate data inputs and pre-baked visualization components into your web app. You still have all of the normal tools of web development at your disposal: HTML, JSX, React components, CSS styles, JavaScript fucntions and imports, etc.

While data loaders for Observable can be written in any programming language, a basic level of comfort with web development concepts (i.e HTML, CSS, and JavaScript) will allow you to make the best use of Observable’s many features.

Example Code

import * as Plot from 'npm:@observablehq/plot';

export function temperaturePlot(data, { width } = {}) {
	return Plot.plot({
		title: 'Hourly temperature forecast',
		width,
		x: { type: 'utc', ticks: 'day', label: null },
		y: { grid: true, inset: 10, label: 'Degrees (F)' },
		marks: [
			Plot.lineY(data.properties.periods, {
				x: 'startTime',
				y: 'temperature',
				z: null, // varying color, not series
				stroke: 'temperature',
				curve: 'step-after'
			})
		]
	});
}
# Weather report

```js
import { temperaturePlot } from './components/temperaturePlot.js';
```

```js
const forecast = FileAttachment('./data/forecast.json').json();
```

```js
display(temperaturePlot(forecast));
```
Observable screenshot

A good choice if:

Not recommended if:

Shiny

Shiny logo

An opinionated wrapper around R and Python, with a focus on efficient reactivity

If R or Python is your data science language of choice, and you’re not interested in doing web development proper, or manually managing callbacks, then it may be worth the effort to learn Shiny’s mental models. Of all the tools reviewed in this article, it is probably the most opinionated in terms of creating new Shiny-specific concepts for the user to learn. For example, all user inputs (i.e. dropdowns) are defined via ui.input_*() functions, and all outputs are created by decorators like @render.plot. Even for an experienced Python developer, all of these concepts can take a second to click. Code for a complex Shiny dashboard can get quite cumbersome.

The upshot of all this is that Shiny automatically manages efficient reactivity for you. Their documentation even gives an example of reproducing a Streamlit dashboard for faster performance.

HTML, CSS, and JavaScript can all be managed manually, but the need for these to live inside Python wrappers can make code look a bit clunky.

If you’re happy to use Shiny’s clean, minimal pre-styled components, and prize efficient reactivity, Shiny could be a good choice.

Example Code

from shiny.express import input, render, ui
from shinywidgets import render_plotly

ui.page_opts(title="Penguins dashboard", fillable=True)

with ui.sidebar():
    ui.input_selectize(
        "var", "Select variable",
        ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "year"]
    )
    ui.input_numeric("bins", "Number of bins", 30)

with ui.card(full_screen=True):
    @render_plotly
    def hist():
        import plotly.express as px
        from palmerpenguins import load_penguins
        return px.histogram(load_penguins(), x=input.var(), nbins=input.bins())
Shiny screenshot

A good choice if:

Not recommended if:

Quarto

Quarto logo

A minimalist Jupyter / Markdown page renderer, aimed at scientific and technical publishing

If your aim is to render data science outputs to HTML files, .doc, or PDF as quickly as possible without any frills, Quarto may be a good choice. Quarto takes Jupyter notebooks or Quarto flavoured Markdown and renders it into a wide variety of formats. Theming is available, as well as some options for interactivity. In fact, if you’re willing to put in the legwork to peruse it, Quarto provides documentation for most things that you may want to do. Overall, however, Quarto is a good choice for those who are already comfortable in Jupyter notebooks, and want to quickly render their work to a shareable format without too much customization or fuss.

If you’re used to publishing your work in LaTeX, Quarto may also appeal as a more modern, flexible alternative which still offers the clean, simple, academic look of a LaTeX document.

Example Code

---
title: 'Quarto Basics'
format:
  html:
    code-fold: true
jupyter: python3
---

For a demonstration of a line plot on a polar axis, see @fig-polar.

```{python}
#| label: fig-polar
#| fig-cap: "A line plot on a polar axis"

import numpy as np
import matplotlib.pyplot as plt

r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
fig, ax = plt.subplots(
  subplot_kw = {'projection': 'polar'}
)
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2])
ax.grid(True)
plt.show()
```
Quarto screenshot

A good choice if:

Not recommended if:

Conclusion

When choosing a BI-as-code tool, consider your team’s technical skills and specific needs:

If you’d like to try Evidence for yourself, you can get started for free.