Three problems from three corners of the work: a Bayesian question, a production question, and an AI-tooling question. Each excerpt is the heart of the answer, not the whole of it; the session is where the rest lives. The stages these sit in are on the career page.
Problem 1 · Bayesian
A hitter is slugging .650 in April. Hot start, or real?
The problem
Forty plate appearances into the season, one hitter is far above his career line. The front office wants one number for how much of that to believe, and it wants the same method to work for every hitter on the roster, including the one with twelve plate appearances.
The approach
Treat every hitter as a draw from a league-wide distribution and let the data decide how far each one is pulled toward it. A hierarchical model does that in one step: hitters with little data shrink most, hitters with a long record barely move, and the posterior for each is an interval, not a point. The question becomes how much of the April line survives the pooling.
import pymc as pm
with pm.Model() as hitters:
mu = pm.Normal("mu", 0.0, 1.0) # league-wide talent, logit scale
tau = pm.HalfNormal("tau", 1.0) # spread of true talent
theta = pm.Normal("theta", mu, tau, shape=n_hitters)
pm.Binomial("hits", n=pa, p=pm.math.invlogit(theta), observed=hits)
trace = pm.sample(2000, tune=1000, target_accept=0.9)
# how much of the hot start survives the pooling
post = pm.math.invlogit(trace.posterior["theta"][..., hot_idx])
Where it is taught: Hierarchical Modeling using PyMC in Baseball. The long form, in Stan, is Becoming a BayeZian; diagnosing whether a chain like this one can be trusted is MCMC in Practice.
Problem 2 · Shiny and production
One slow query freezes the app for the whole staff.
The problem
The scouting app pulls a season of tracking data when a user picks a player. The query takes nine seconds. While it runs, every other person using the app sees a frozen screen, because a Shiny session is single-threaded and the server is waiting with it.
The approach
Move the slow work off the session's thread with an ExtendedTask backed by a mirai daemon, so the query runs elsewhere and the UI stays responsive for everyone. Show a state while it runs, invalidate the result when the input changes, and keep the data access in one module so the fix lives in one place.
library(shiny); library(mirai)
daemons(4)
server <- function(input, output, session) {
fetch <- ExtendedTask$new(function(player_id) {
mirai({ load_tracking(player_id) }, player_id = player_id)
}) |> bind_task_button("go")
observeEvent(input$go, fetch$invoke(input$player))
output$status <- renderText(if (fetch$status() == "running") "Loading…" else "")
output$plot <- renderPlot({ plot_tracking(fetch$result()) })
}
Where it is taught: The Pit Stop. Sharing the result between modules without a global is The No-Look Pass; the app's structure at scale is The Farm System; shipping it is ProductioniZing Shiny Applications.
Problem 3 · LLM tooling
Let the staff ask the box score a question in plain English.
The problem
A coach wants to know who led the playoffs in points per minute, without filing a request and waiting a day. The data is a clean table. The gap is the SQL, and the person asking does not write it.
The approach
Put a chat interface over the table with querychat: the model writes the SQL, the SQL runs against your data, and the result comes back as a table the user can see and check. The model never sees the rows, only the schema, so the answer is grounded in your data and every query is visible and reproducible. The work is in the schema description and the guardrails, not the prompt.
library(shiny); library(querychat)
qc <- querychat_init(
df = wnba_box,
table_name = "box",
greeting = "Ask about any playoff box score.",
data_description = readLines("schema.md") # columns, units, what a row is
)
ui <- page_sidebar(sidebar = querychat_sidebar("chat"), tableOutput("rows"))
server <- function(input, output, session) {
chat <- querychat_server("chat", qc)
output$rows <- renderTable(head(chat$df(), 25)) # the SQL the model wrote, run on your data
}
Where it is taught: Ask the Box Score Anything. Turning unstructured text into rows with an agent is Build an AI Agent That Turns Cricket Commentary Into Data; the full retrieval pipeline in R is SynergiZing ML & LLMs in R.