<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Python on Posit Open Source</title>
    <link>https://opensource.posit.co/languages/python/</link>
    <description>Recent content in Python on Posit Open Source</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 13 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://opensource.posit.co/languages/python/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Positron August Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/</link>
      <pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release">Last release</a> we told you about several major features that came out of preview to general availability, including the <a href="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga">new notebook editor</a>. This milestone we are excited to share new functionality for SQL support, reproducible authoring, helping you know when packages are missing, and more.</p>
<h2 id="data-connections">Data Connections
</h2>
<p>Data Connections is our new way to work with SQL and database-like resources in Positron, from local files and database servers to cloud data warehouses. It is currently available as a preview feature, and you can enable it with the <a href="positron://settings/dataConnections.enabled"><code>dataConnections.enabled</code></a> setting. This release more than doubles the number of data sources you can reach. Amazon Redshift, Snowflake, Databricks, and Posit Connect pins join the existing DuckDB, PostgreSQL, and SQLite support.</p>
<img src="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/data-connections.gif" data-fig-align="center" data-fig-alt="Browsing the schemas and tables of a DuckDB connection in the Data Connections panel, then opening a table in the Data Explorer to see its column profiles and data." />
<p>The panel itself is more capable as well. <strong>Refresh</strong> and <strong>Refresh All</strong> reload the tree while preserving what you have expanded, briefly highlighting the rows that were reloaded. Open connections now show an indicator. Collapsing a connection in the UI keeps you connected to your data source, while anything you&rsquo;ve previewed with the Data Explorer stays still open. When you remove a connection, Positron asks for confirmation and reports how many Data Explorers will close with it.</p>
<p>Data Connections is still an experimental preview, and your feedback continues to shape it. Tell us which databases and warehouses you need, and anything confusing, missing, or broken, in the <a href="https://github.com/posit-dev/positron/discussions/14571" target="_blank" rel="noopener">Data Connections discussion</a>.</p>
<h2 id="inline-output-for-quarto">Inline output for Quarto
</h2>
<p><a href="https://positron.posit.co/quarto-inline-output" target="_blank" rel="noopener">Inline output for <code>.qmd</code> documents</a> came out of preview last release, and this release brings you a substantial round of polish for this way of working. Be aware that the Quarto settings have moved into a dedicated <code>quarto.*</code> namespace with its own group in the Settings editor. The previous <code>positron.quarto.*</code> keys still work but are deprecated, and Positron will prompt you to update your settings.</p>
<p>Before a kernel starts, the kernel status names the interpreter it will start and offers an explicit <strong>Start Kernel</strong> action. When a cell fails, <strong>Fix</strong> and <strong>Explain</strong> buttons send the error to <a href="https://assistant.posit.co/" target="_blank" rel="noopener">Posit Assistant</a>, matching the Positron notebook experience. The editor also scrolls to reveal output as it is produced, which you can turn off with the new <a href="positron://settings/quarto.inlineOutput.autoScroll"><code>quarto.inlineOutput.autoScroll</code></a> setting.</p>
<p>Output renders more faithfully as well. The editor gutter now shows which statement is currently executing and per-statement progress. Positron draws images at your display&rsquo;s pixel ratio, so plots are sharp on retina screens, and Python figures now respect the <code>fig-width</code> and <code>fig-height</code> cell options.</p>
<img src="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/inline-output-plot-metadata.png" data-fig-align="center" data-fig-alt="A Quarto document open in Positron with a Python cell that sets the fig-width and fig-height options, and the resulting matplotlib scatter plot rendered inline below the cell at that size." />
<p>HTML widgets no longer stick in the editor corner when you scroll past them, or trap scrolling instead of letting the document scroll. HTML widgets no longer render as raw HTML after a reload, and collapsed output no longer springs back open when its cell re-runs. Running code in a Quarto document also pins the editor tab now, so Positron does not silently close the document and its session when you open another file.</p>
<h2 id="ai-model-providers">AI model providers
</h2>
<p>Positron now reads AI model provider configuration from a single <code>providers.json</code> file rather than a scattered set of settings. The new release will migrate your existing configuration automatically when you start it, and deprecates the <code>authentication.*</code> and <code>positron.assistant.provider.*.enable</code> settings in favor of it. Two new commands give you direct access: <em>Open AI Provider Settings (JSON)</em> opens <code>providers.json</code> from the Command Palette, and <em>Migrate Provider Settings to providers.json</em> runs the migration on demand.</p>
<h2 id="install-missing-packages">Install missing packages
</h2>
<p>Positron now notices when your code refers to a package you do not have installed and offers to install it for you. The prompt appears for packages referenced by your scripts and notebooks in both R and Python.</p>
<img src="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/missing-package.gif" data-fig-align="center" data-fig-alt="A Shiny app in Positron showing a missing package button in the editor toolbar. Clicking it installs bslib in the console, and the app then runs with its bubble chart in the Viewer pane." />
<p>A <code>library()</code> or <code>import</code> call for something missing becomes a single click instead of an error you have to go resolve by hand.</p>
<h2 id="performance-and-memory">Performance and memory
</h2>
<p>We continue to invest in the memory footprint, performance, and reliability of Positron. Several components now load only when they are actually needed, and turning off <a href="positron://settings/ai.enabled"><code>ai.enabled</code></a> now means Positron never loads some heavy AI-related components at all. We fixed a cluster of long-standing reliability problems around session restarts and lifecycles.</p>
<p>Startup and editing are faster as well. R and Python kernels start much faster on Windows systems with aggressive antivirus software. The interpreter session picker appears immediately instead of waiting for interpreter discovery to finish. We also fixed slow typing, formatting, and saving in long R and Python files.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>Join <a href="https://posit.co/workflow-demo/ai-governance-workbench" target="_blank" rel="noopener">our upcoming webinar</a> on August 26 to learn about AI governance in Posit Workbench.</li>
<li>We are looking forward to posit::conf(2026) next month, where our team will have several sessions on Positron. <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/featured.svg" length="91423" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>posit::glimpse() Newsletter – August 2026</title>
      <link>https://opensource.posit.co/blog/2026-08-05_glimpse-aug-2026/</link>
      <pubDate>Wed, 05 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-08-05_glimpse-aug-2026/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<blockquote>
<p>Welcome to our newsletter, posit::glimpse()!</p>
<p>If you&rsquo;re currently reading this on our blog, consider subscribing to Product Updates - Open Source on our <a href="https://posit.co/about/subscription-management" target="_blank" rel="noopener">subscription page</a> to receive this newsletter directly in your inbox.</p>
</blockquote>
<p>Every month, we round up some of the most important open-source news for Posit&rsquo;s community! We hope that you enjoy this newsletter and maybe even share it with a friend.</p>
<p>The table of contents on the right can help you navigate through all the updates. As you scroll, it will open up to show you subcategories →</p>
<h2 id="announcements">Announcements
</h2>
<h3 id="register-for-positconf2026">Register for posit::conf(2026)
</h3>
<p>Register for our annual conference, <a href="https://posit.co/conference/" target="_blank" rel="noopener">posit::conf(2026)</a>, happening September 14-16 in Houston and online! The amazing <a href="https://posit.co/blog/posit-conf-2026-agenda-breakdown" target="_blank" rel="noopener">speakers</a> and <a href="https://posit.co/blog/workshops-at-positconf2026" target="_blank" rel="noopener">workshops</a> will make for a wonderful time. <a href="https://conf.posit.co/2026/registration/" target="_blank" rel="noopener">Register for posit::conf here</a>.</p>
<p>For the first time, the live streams will feature automatically translated subtitles, available in French, German, Italian, Portuguese, and Spanish.</p>
<p>Tidy Dev Day is on September 17, a unique opportunity to collaboratively tackle open-source issues and work directly alongside the very developers who build and maintain the tools you use every day. <a href="https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/" target="_blank" rel="noopener">Learn more about Tidy Dev Day here</a>.</p>
<h3 id="migrating-to-connect-cloud">Migrating to Connect Cloud
</h3>
<p>A thank-you to everyone who published on rpubs.com, quartopub.com, shinyapps.io, and bookdown.org. We&rsquo;re consolidating all our publishing tools into one: <a href="https://connect.posit.cloud/" target="_blank" rel="noopener">Posit Connect Cloud</a>. Connect Cloud is built to handle all of the content types in one place, with one account, and we hope to make your transition as smooth as possible.</p>
<ul>
<li>Learn more in the <a href="https://posit.co/blog/migrating-connect-cloud-posits-unified-publishing-solution" target="_blank" rel="noopener">Migrating to Connect Cloud: Posit&rsquo;s unified publishing solution blog post</a>.</li>
<li>Join our <a href="https://posit.co/webinar/connect-cloud-migration" target="_blank" rel="noopener">webinar on the migration process happening September 9th</a>.</li>
</ul>
<h2 id="key-product-updates-and-new-releases">Key product updates and new releases
</h2>
<h3 id="httr2-130">httr2 1.3.0
</h3>
<p><a href="https://httr2.r-lib.org/" target="_blank" rel="noopener">httr2</a> is a comprehensive HTTP client for R for working with web APIs. httr2 1.3.0 introduces a breaking change to OAuth token caching that requires one-time re-authentication. Recent patches added 200x faster streaming for short-line responses, OAuth server metadata discovery, OpenTelemetry tracing support, and <code>httr2_translate()</code> for converting requests to curl commands.</p>
<ul>
<li>Read more in the <a href="https://opensource.posit.co/blog/2026-07-14_httr2-1-3-0/" target="_blank" rel="noopener">httr2 1.3.0 blog post</a>.</li>
</ul>
<h3 id="ir-010">ir 0.1.0
</h3>
<p>Introducing <a href="https://r-lib.github.io/ir/" target="_blank" rel="noopener">ir</a> 0.1.0, a command-line tool for running self-describing R scripts and Quarto documents with embedded package dependencies and R version requirements, inspired by Python&rsquo;s PEP 723 and <code>uv run --script</code>. The tool supports isolated environments, CRAN snapshot dates, mixed R/Python workflows, and caches resolved dependencies for efficiency, enabling one-file workflows without full project structures.</p>
<ul>
<li>Read more in the <a href="https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/" target="_blank" rel="noopener">ir 0.1.0 blog post</a>.</li>
</ul>
<h3 id="lorax">lorax
</h3>
<p><a href="https://lorax.tidymodels.org/" target="_blank" rel="noopener">lorax</a> introduces a unified interface for characterizing tree- and rule-based models across 12 implementations including ranger, XGBoost, LightGBM, and random forests. The package enables extracting decision rules, identifying active predictors, converting trees to partykit format for visualization, and accessing variable importance scores through consistent APIs that work across diverse tree model packages.</p>
<ul>
<li>Read more in the <a href="https://opensource.posit.co/blog/2026-07-28_lorax/" target="_blank" rel="noopener">Introducing lorax: Speaking for the Tree-Based Models blog post</a>.</li>
</ul>
<h3 id="mcptools-100">mcptools 1.0.0
</h3>
<p><a href="https://posit-dev.github.io/mcptools/" target="_blank" rel="noopener">mcptools</a>, an R SDK for the Model Context Protocol, is now on CRAN. It brings image support for rich content in both directions, native authenticated remote server connections eliminating the npm dependency, and streamlined <a href="https://posit.co/products/enterprise/connect" target="_blank" rel="noopener">Posit Connect</a> deployment via <code>_server.yml</code>. This R SDK for the Model Context Protocol enables deploying R functions as MCP servers and fetching third-party MCP tools as R functions.</p>
<ul>
<li>Read more in the <a href="https://opensource.posit.co/blog/2026-07-06_mcptools-1-0-0/" target="_blank" rel="noopener">mcptools 1.0.0 blog post</a>.</li>
</ul>
<h3 id="quarto-110">Quarto 1.10
</h3>
<p><a href="https://quarto.org/" target="_blank" rel="noopener">Quarto</a> transforms Markdown, code, and computational output into publication-ready articles, reports, presentations, websites, and books. Version 1.10 introduces offline HTML accessibility checking with bundled axe-core and WCAG conformance level targeting, plus localized string support for template authors. The release includes important fixes for <code>quarto preview</code> reliability, shortcode resolution in math expressions, and updated dependencies including pandoc 3.10 and typst 0.15.1.</p>
<ul>
<li>Read more in the <a href="https://opensource.posit.co/blog/2026-08-03_quarto-1-10/" target="_blank" rel="noopener">Quarto 1.10 blog post</a>.</li>
</ul>
<h3 id="raghilda-v02">raghilda v0.2
</h3>
<p><a href="https://posit-dev.github.io/raghilda/" target="_blank" rel="noopener">Raghilda</a> is a Python package for building RAG (Retrieval-Augmented Generation) solutions. This release focuses on production-ready features for scaling RAG pipelines, with crawl and ingest API with caching and concurrency, a CloudflareCrawler for JavaScript-rendered sites, a PostgreSQL store backend, and NVIDIA NIM embeddings.</p>
<ul>
<li>Check out the <a href="https://opensource.posit.co/blog/2026-07-01_raghilda-0-2-0/" target="_blank" rel="noopener">raghilda v0.2 blog post</a>.</li>
</ul>
<h3 id="roxygen2-810">roxygen2 8.1.0
</h3>
<p><a href="https://roxygen2.r-lib.org/" target="_blank" rel="noopener">roxygen2</a> 8.1.0 improves performance with consolidated <code>importFrom()</code> directives that reduce package loading time from ~120ms to ~9ms for 1,000 imports, and introduces the rdtools package for faster cross-reference resolution during documentation generation. The release also adds multi-line support for <code>@importFrom</code> directives with hanging indents.</p>
<ul>
<li>Check out the <a href="https://opensource.posit.co/blog/2026-08-04_roxygen2-8-1-0/" target="_blank" rel="noopener">roxygen2 8.1.0 blog post</a>.</li>
</ul>
<h3 id="shiny-updates">Shiny updates
</h3>
<p><a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a> is the framework for building interactive web applications in R and Python. Shiny for R 1.14 adds <code>startApp()</code> for non-blocking app launches and <code>session$destroy()</code> for proper module cleanup. Shiny for Python 1.7 includes bundled Agent Skills for coding agents and test mode with live JSON snapshots. bslib 0.12 introduces offcanvas panels that slide in from viewport edges, plus direct sidebar handle resizing.</p>
<ul>
<li>Read more in the <a href="https://opensource.posit.co/blog/2026-08-04_shiny-r-1-14-python-1-7/" target="_blank" rel="noopener">Shiny updates blog post</a>.</li>
</ul>
<h2 id="positron-learning-resources">Positron learning resources
</h2>
<p>The Positron team is on a roll. Here are the key features from the <a href="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/" target="_blank" rel="noopener">July Release Highlights</a>:</p>
<ul>
<li><strong>The Positron Notebook Editor now serves as the default option for .ipynb files</strong>: You get a fully integrated experience for Jupyter Notebooks out of the box. Key tools like environment management, version control, and AI coding support work automatically the moment you open any .ipynb file.</li>
<li><strong>The Packages pane has officially transitioned out of preview status</strong>: Inspect, update, and explore installed libraries without leaving your IDE or relying on Terminal commands.</li>
<li><strong>Posit Assistant has reached general availability</strong>: AI assistance is enterprise-ready: enjoy full-fledged coding assistance, debugging help, and inline explanations directly in your workflow with improved stability and performance.</li>
<li><strong>Support for opening Excel workbooks has been added to Data Explorer</strong>: No need to export to CSV or switch to Microsoft Excel to check your data: preview, filter, inspect .xlsx sheets directly inside Positron before running any code.</li>
<li><strong>Enhanced language intelligence for R</strong>: Navigating large R projects is much faster and less error-prone with features like Go to Definition, Find References, and Rename Symbol that work across all packages and scripts in your workspace. Diagnostics update instantly even when files change externally.</li>
</ul>
<p>The team has also shared several blog posts packed with tips and tricks for getting the most out of Positron:</p>
<ul>
<li><a href="https://opensource.posit.co/blog/2026-07-08_positron-uv/" target="_blank" rel="noopener">Discover how Positron paired with uv configures your Python environment in just one click</a></li>
<li><a href="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/" target="_blank" rel="noopener">Explore strategies for seamlessly managing your R and Python environments within Positron</a></li>
</ul>
<h2 id="lets-learn-about-ai">Let&rsquo;s learn about AI
</h2>
<p><strong>You can now receive the AI newsletter via email!</strong> Paste the newsletter&rsquo;s <a href="https://opensource.posit.co/tags/ai-newsletter/index.xml" target="_blank" rel="noopener">RSS feed URL</a> into a free RSS-to-email service like <a href="https://blogtrottr.com/" target="_blank" rel="noopener">https://blogtrottr.com/</a> to receive the newsletter by email.</p>
<ul>
<li>AGENTS.md, Skills, MCP servers, oh my! If you&rsquo;ve heard all these terms and want to investigate the differences, check out the <a href="https://opensource.posit.co/blog/2026-07-03_ai-newsletter/" target="_blank" rel="noopener">July 3rd edition of the AI Newsletter</a>.</li>
<li>LLMs often miss subtle visual artifacts in data visualizations, and there are ways of evaluating their oversights. Learn about bluffbench in the <a href="https://opensource.posit.co/blog/2026-07-17_ai-newsletter/" target="_blank" rel="noopener">July 17th edition of the AI Newsletter</a>.</li>
<li><a href="https://assistant.posit.co/" target="_blank" rel="noopener">Posit Assistant</a> in Positron now includes an EDA log feature to help you keep track of exploratory analysis done with the agent. Learn more in the <a href="https://opensource.posit.co/blog/2026-07-31_ai-newsletter/" target="_blank" rel="noopener">July 31st edition of the AI newsletter</a>.</li>
</ul>
<h2 id="do-you-want-event-recordings-you-got-them">Do you want event recordings? You got them!
</h2>
<p>&ldquo;Will this be recorded?&rdquo; If it is, you can access our recordings directly on the <a href="https://opensource.posit.co/events/" target="_blank" rel="noopener">event pages</a>, and we do our best to post them as quickly as possible.</p>
<p>Be sure to catch the presentations featured below, including Teun van den Brand&rsquo;s session on ggsql and Rich Iannone&rsquo;s presentation on Great Docs:</p>















  
  
  
  
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  



<div class="grid grid-cols-3 gap-6 xl:gap-8 xl:gap-8 mb-6">
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  






















  
    
  





  
    
  
























<div id="item-de299f206b1033e586cb69ebed60c69b" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/" title="DuckCon #7 – ggsql: A grammar of graphics for SQL (Teun van den Brand)"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_b2e1015481833d64.webp 1x,
                  /resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_a676e22e82ce813f.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_897602341fe091b4.jpg"
          srcset="/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_897602341fe091b4.jpg 1x,
                  /resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_13025d5418a80e9d.jpg 2x"
          alt="DuckCon #7 – ggsql: A grammar of graphics for SQL (Teun van den Brand)"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_a676e22e82ce813f.webp 1x,
                  /resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_c76545a5a0e054b1.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_13025d5418a80e9d.jpg"
          srcset="/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_13025d5418a80e9d.jpg 1x,
                  /resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_f18dd7e1387eef85.jpg 2x"
          alt="DuckCon #7 – ggsql: A grammar of graphics for SQL (Teun van den Brand)"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_c76545a5a0e054b1.webp 1x,
                  /resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_bf6653beaa56c5aa.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_f18dd7e1387eef85.jpg"
          srcset="/resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_f18dd7e1387eef85.jpg 1x,
                  /resources/videos/2026-06-30_duckcon-7-ggsql-a-grammar-of-graphics-for-sql-teun-van-den-brand/thumbnail_hu_50c1b8a79d2695c2.jpg 2x"
          alt="DuckCon #7 – ggsql: A grammar of graphics for SQL (Teun van den Brand)"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 30, 2026</span>
          <span>|</span>
        
        
        
          <span>12 min</span>
          <span>|</span>
        
        
          <span>2.1k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">DuckCon #7 – ggsql: A grammar of graphics for SQL (Teun van den Brand)</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">Event: DuckCon #7 (DuckDB and DuckLake community conference), https://duckdb.org/events/2026/06/24/duckcon7/ Speaker: Teun van den Brand (Posit PBC) Slides: https://blobs.duckdb.org/events/duckcon7/teun-van-den-brand-ggsql.pdf</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/teun-van-den-brand/teun-van-den-brand.jpg" 
          alt="Teun Van den Brand" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Teun Van den Brand</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                ggsql
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-ca048271111b051a31910a26462be382" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/" title="Great Docs: The Future of Documentation | The Python Exchange June 2026"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_4a79da9b97975bb1.webp 1x,
                  /resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_7c3afa6c6493dda.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_ad0f5cdbfef37964.jpg"
          srcset="/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_ad0f5cdbfef37964.jpg 1x,
                  /resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_c974e8c34f17a663.jpg 2x"
          alt="Great Docs: The Future of Documentation | The Python Exchange June 2026"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_7c3afa6c6493dda.webp 1x,
                  /resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_e2f3f8ee4b22f520.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_c974e8c34f17a663.jpg"
          srcset="/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_c974e8c34f17a663.jpg 1x,
                  /resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_af5a09ce00461bfb.jpg 2x"
          alt="Great Docs: The Future of Documentation | The Python Exchange June 2026"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_e2f3f8ee4b22f520.webp 1x,
                  /resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_6c04a9d6eaf920ed.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_af5a09ce00461bfb.jpg"
          srcset="/resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_af5a09ce00461bfb.jpg 1x,
                  /resources/videos/2026-07-06_great-docs-the-future-of-documentation-the-python-exchange-june-2026/thumbnail_hu_991395b2a0842829.jpg 2x"
          alt="Great Docs: The Future of Documentation | The Python Exchange June 2026"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jul 6, 2026</span>
          <span>|</span>
        
        
        
          <span>1h 5min</span>
          <span>|</span>
        
        
          <span>114 views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Great Docs: The Future of Documentation | The Python Exchange June 2026</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">About the Python Exchange for the Department of Energy: https://meetup.doepy.org/ Sign up here to be notified when the next Python Exchange is scheduled: https://bit.ly/py-exchange
Guest Panelist: Rich Iannone
Title: Great Docs: Building the Documentation Site Generator I Always Wanted
Bio: Rich Iannone Rich is a software engineer and someone who cares a great deal about documentation and summary tables. He&rsquo;s been working on table presentation and documentation packages for quite a long time, and nobody has told him to stop yet, so he&rsquo;s still on top of all that. He started off mostly creating open-source packages in R, but more lately, he&rsquo;s been doing open-source things in Python. Rich really enjoys creating all these packages because he realizes that people can use &rsquo;em to do great things in their own work.
Abstract: Documentation generators have, for me, been a thing where there are so many trade-offs. They might make beautiful sites, but be shallow feature-wise. Or full of features but really hard to configure. After some years of working across R, Python, and the broader open-source ecosystem, I started Great Docs with a simple end goal: your docs should look really great, be easy to discover, and be ready for the AI era.
In this presentation, I will walk through the various places I drew inspiration for Great Docs (pkgdown, MATLAB, Elixir, and the Vue.js docs). I&rsquo;ll show how Great Docs makes documentation consumable by both humans and AI agents through Skills and MCP server documentation. I&rsquo;ll share some of the features I obsessively built because I wanted them in my own package docs. Finally, I&rsquo;ll discuss where this project is headed beyond Python (toward documenting CLIs, TUIs, and more).
At Don&rsquo;t Use This Code, we want to create a unique opportunity to see Python succeed and thrive within the DOE. In collaboration with an organizing committee of Python users and advocates from various DOE sites, we host regular conversations on the topic of Python in order to create a new resource for scientists, researchers, and technical staff. Our goal is to share best practices and build a strong, lasting community for Python users within the Department of Energy National Lab system.
If you, or someone you know, might make a great panelist, we&rsquo;d love to get in touch! Just send us an email at DOEPy@dutc.io if you would like to participate or have any questions
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/rich-iannone/rich-headshot.png" 
          alt="Rich Iannone" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Rich Iannone</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                Great Docs
              </span>
            
              <span class="pill">
                pkgdown
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
</div>


<p>Additionally, we have some fantastic recordings from the <a href="https://pos.it/dslab" target="_blank" rel="noopener">Data Science Lab</a>. Watch the Lab Managers share their expertise while live coding:</p>















  
  
  
  
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  



<div class="grid grid-cols-3 gap-6 xl:gap-8 xl:gap-8 mb-6">
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-9b757f4d2908c3491363411a7cadd25a" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/" title="Live TidyTuesday Data Viz Workflow | Nicola Rennie | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_91cdc9bfec4beb.webp 1x,
                  /resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_2bdf2408bfb74ae8.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_1cb3889dbd947c4c.jpg"
          srcset="/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_1cb3889dbd947c4c.jpg 1x,
                  /resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_436953f457f71693.jpg 2x"
          alt="Live TidyTuesday Data Viz Workflow | Nicola Rennie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_2bdf2408bfb74ae8.webp 1x,
                  /resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_c0da8212f74d2e17.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_436953f457f71693.jpg"
          srcset="/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_436953f457f71693.jpg 1x,
                  /resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_ba0631db832f77db.jpg 2x"
          alt="Live TidyTuesday Data Viz Workflow | Nicola Rennie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_c0da8212f74d2e17.webp 1x,
                  /resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_830f9934dc51456c.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_ba0631db832f77db.jpg"
          srcset="/resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_ba0631db832f77db.jpg 1x,
                  /resources/videos/2026-07-24_live-tidytuesday-data-viz-workflow-nicola-rennie-data-science-lab/thumbnail_hu_e8ac5809a85eaadf.jpg 2x"
          alt="Live TidyTuesday Data Viz Workflow | Nicola Rennie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jul 24, 2026</span>
          <span>|</span>
        
        
        
          <span>51 min</span>
          <span>|</span>
        
        
          <span>48 views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Live TidyTuesday Data Viz Workflow | Nicola Rennie | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Nicola Rennie, who walks through her process for making a TidyTuesday data visualization in R: live coding a chart from scratch and sharing the workflow tricks she&rsquo;s built up over five years of doing TidyTuesday!
Nicola takes this week&rsquo;s TidyTuesday &ldquo;twin cities&rdquo; dataset and builds a colorful sunburst (radial) chart from scratch, showing how she decides what to plot, wrangles the data with dplyr, and makes it pretty using showtext fonts, rcartocolor palettes, colored-text legends, and the ggview package for previewing plots at exactly the size they&rsquo;ll save at. Along the way she shares the templates and helper packages she&rsquo;s made to save time every week (can you tell she&rsquo;s a pro?!). Since TidyTuesday data is public, you can grab the same dataset and follow along, too!
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Socials and websites: Nicola Rennie&rsquo;s blog: https://nrennie.rbind.io/ Nicola Rennie&rsquo;s GitHub: https://github.com/nrennie Nicola Rennie&rsquo;s Bluesky: https://bsky.app/profile/nrennie.bsky.social Nicola Rennie&rsquo;s LinkedIn: https://www.linkedin.com/in/nicola-rennie/ Nicola Rennie&rsquo;s Mastodon: https://fosstodon.org/deck/@nrennie
Resources mentioned in the video and chat: Nicola&rsquo;s code from this session: https://nrennie.rbind.io/talks/posit-ds-lab/ TidyTuesday on GitHub: https://github.com/rfordatascience/tidytuesday Nicola&rsquo;s templates package: https://github.com/nrennie/templates showtext package (fonts in R): https://github.com/yixuan/showtext ggview package (preview plots at save size): https://github.com/idmn/ggview rcartocolor package (accessible color palettes): https://nowosad.r-universe.dev/rcartocolor ggtext package (styled/colored text): https://wilkelab.org/ggtext/ ggiraph package (interactive ggplots): https://davidgohel.github.io/ggiraph/ marquee package (markdown text in R graphics): https://marquee.r-lib.org/ Nicola&rsquo;s book, The Art of Data Visualization with ggplot2: https://nrennie.rbind.io/art-of-viz/ Bootstrap Icons: https://icons.getbootstrap.com/ okpalette (extract a palette from a photo): https://okpalette.color.pizza/
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu Follow Us Here: Website: https://www.posit.co Hangout: https://pos.it/dsh The Lab: https://pos.it/dslab LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for hanging out with us!
Timestamps 00:00 Introduction 01:18 Reviewing the TidyTuesday twin cities dataset 03:41 Demoing TidyTuesday template files and R package setup 07:25 &ldquo;Do showtext and systemfonts do different things?&rdquo; 09:04 Loading and prepping the links and cities datasets 11:45 Exploring and filtering the dataset to a chosen city 17:25 Demoing a Shiny app to copy previous TidyTuesday chart code 23:56 Demoing sunburst chart creation with coord_radial 25:47 &ldquo;Can you say why you used mutate rather than summarize?&rdquo; 31:09 Adding custom colors with the rcartocolor package 33:57 Demoing colored subtitle text with the ggtext package 40:38 Attempting to make the chart interactive with the ggiraph package 43:32 Adding automated social and source captions 46:26 &ldquo;So you can save a plot with the canvas call and it doesn&rsquo;t mess up the ggplot object?&rdquo; 49:55 &ldquo;If people would like to keep up with you and see your posts, what&rsquo;s the best way to do that?&rdquo;
</p>
      

      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                dplyr
              </span>
            
              <span class="pill">
                ggplot2
              </span>
            
              <span class="pill">
                Shiny
              </span>
            
              <span class="pill">
                systemfonts
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-d40c591fd8755cf94c245b16a57ac876" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/" title="Mind-Blowing Quarto Slide Extensions 💬 🖍️ 🤯 | Emil Hvitfeldt | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_a4ef79aec4159f0b.webp 1x,
                  /resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_12eca1b1ff66693f.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_400ddecd3f838eab.jpg"
          srcset="/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_400ddecd3f838eab.jpg 1x,
                  /resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_b22a6dec3c217c9d.jpg 2x"
          alt="Mind-Blowing Quarto Slide Extensions 💬 🖍️ 🤯 | Emil Hvitfeldt | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_12eca1b1ff66693f.webp 1x,
                  /resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_b4bdbf1c5b2fd5df.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_b22a6dec3c217c9d.jpg"
          srcset="/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_b22a6dec3c217c9d.jpg 1x,
                  /resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_c7f3360f4b455ac6.jpg 2x"
          alt="Mind-Blowing Quarto Slide Extensions 💬 🖍️ 🤯 | Emil Hvitfeldt | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_b4bdbf1c5b2fd5df.webp 1x,
                  /resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_e2aa1e91b2f546a2.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_c7f3360f4b455ac6.jpg"
          srcset="/resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_c7f3360f4b455ac6.jpg 1x,
                  /resources/videos/2026-07-23_mind-blowing-quarto-slide-extensions-emil-hvitfeldt-data-science-lab/thumbnail_hu_d69b5bc0a20d0f5b.jpg 2x"
          alt="Mind-Blowing Quarto Slide Extensions 💬 🖍️ 🤯 | Emil Hvitfeldt | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jul 23, 2026</span>
          <span>|</span>
        
        
        
          <span>56 min</span>
          <span>|</span>
        
        
          <span>1.1k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Mind-Blowing Quarto Slide Extensions 💬 🖍️ 🤯 | Emil Hvitfeldt | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Emil Hvitfeldt, who walks through the art of &ldquo;slide crafting&rdquo; in Quarto and shows off the collection of Reveal JS extensions he&rsquo;s been building to make slides way more fun and powerful.
Emil starts with the basics of building slides in Quarto with the Reveal JS format (fragments, columns, a little CSS) and explains how Quarto extensions differ from packages. Then he shows off the good stuff: iMessage-style chat bubbles hand-drawn &ldquo;rough notation&rdquo; highlighting and annotations ️ the Flourish extension for animating code chunks, gorgeous reusable themes, and his in-progress &ldquo;editable&rdquo; extension that lets you drag, resize, and restyle slide elements right in the browser and save the changes back to your source. And, spoiler, his &ldquo;editable&rdquo; extension is much further along as of the publishing of this video!! If you&rsquo;ve ever fought with slide layouts, there is definitely something here you can try at home!
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Emil&rsquo;s socials and urls: website: https://emilhvitfeldt.com/ GitHub: https://github.com/emilhvitfeldt Bluesky: https://bsky.app/profile/emilhvitfeldt.bsky.social LinkedIn: https://www.linkedin.com/in/emilhvitfeldt/
Resources mentioned in the video and chat: Emil&rsquo;s GitHub (home of his Quarto extensions): https://github.com/EmilHvitfeldt quarto-revealjs-editable extension: https://github.com/EmilHvitfeldt/quarto-revealjs-editable quarto-revealjs-rows extension (shared by Emil after the session): https://github.com/EmilHvitfeldt/quarto-revealjs-rows Quarto Reveal JS documentation: https://quarto.org/docs/presentations/revealjs/ Quarto Reveal JS demo slides: https://quarto.org/docs/presentations/revealjs/demo/#/title-slide News! Quarto 2 (Quarto in Rust): https://quarto.org/docs/blog/posts/2026-04-06-whats-next-quarto-2/ Dynamic Tabsets in Quarto (Josh Cowley): https://josh.quarto.pub/posts/2022-11-10-quarto-tabsets-update/2022-11-10-quarto-tabsets-update.html Embedding Reveal JS via iframe (Quarto source example): https://github.com/quarto-dev/quarto-web/blob/main/docs/presentations/revealjs/index.qmd#L12-L14 posit::conf 2026: https://conf.posit.co/2026/ R/Medicine conference: https://rconsortium.github.io/RMedicine_website/ Muntasir Masum&rsquo;s slides repo: https://github.com/muntasirmasum/epi553-data-viz Example Quarto slides — Nicola Rennie (Styling Charts): https://nrennie.rbind.io/MFC-CDT-data-viz/slides/styling-charts-r.html Example Quarto slides — Colin Madland: https://cmadland.github.io/slides/ Colin Madland&rsquo;s slides repo: https://github.com/cmadland/slides Example Quarto slides — Raha Dastgheyb (R/Medicine 2024): https://rdastgh1.quarto.pub/rmedicine-2024-scidatareportr/#/title-slide
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu Follow Us Here: Website: https://www.posit.co Hangout: https://pos.it/dsh The Lab: https://pos.it/dslab LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for hanging out with us!
Timestamps 00:00 Introduction 01:22 Demoing a primer on Reveal JS slides in Quarto 05:11 &ldquo;Does that PDF export still use Pandoc, or is there an option of exporting via Typst?&rdquo; 08:17 Demoing slide crafting with a new Quarto document 11:24 Demoing how to download and use a Quarto template 14:01 &ldquo;Is this UI you&rsquo;re using for slide generation exclusive to Positron?&rdquo; 16:16 Demoing how to install a Quarto extension from the terminal 18:45 &ldquo;Is there a maximum number of columns you can have?&rdquo; 19:18 Demoing adding custom CSS classes to slide columns 23:03 &ldquo;Do we know if extensions will still be submitted to the OpenVSX marketplace?&rdquo; 25:11 &ldquo;Are these Quarto extensions global, or are they per project?&rdquo; 27:42 &ldquo;When you change the text fonts inside those columns, could you do it for all the columns at once, maybe by changing the top div?&rdquo; 29:31 &ldquo;Do you also get grid logic like you have with CSS Grid?&rdquo; 31:54 Demoing Quarto VS Code extensions for autocomplete and shortcodes 34:32 Demoing the chat bubbles extension 37:38 Demoing the rough notation extension for highlighting text fragments 40:20 Demoing the Flourish extension for highlighting code chunks 43:07 Demoing the editable extension for dragging and adjusting slide elements 49:28 Demoing custom Reveal JS slide themes 52:26 &ldquo;Is there a Quarto extension that can help dynamically create tabs or chunks?&rdquo; (see dynamic tabs link in the resources above!) 53:58 Explaining the default code chunk highlighting feature in Quarto 55:11 &ldquo;How do you embed a Reveal JS site into another Quarto document?&rdquo;
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/emil-hvitfeldt/profile.jpg" 
          alt="Emil Hvitfeldt" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Emil Hvitfeldt</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                Positron
              </span>
            
              <span class="pill">
                Quarto
              </span>
            
              <span class="pill">
                revealjs
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
</div>


<p>We hope you learn a lot from these resources!</p>
<h2 id="showcases-from-the-community">Showcases from the community
</h2>
<p>We love learning what you are up to. If you have a project using our tools to share, please let us know. In particular, I&rsquo;d love to hear how you&rsquo;re adopting and using Positron! Find me on <a href="https://www.linkedin.com/in/ivelasq/" target="_blank" rel="noopener">LinkedIn</a> and <a href="https://bsky.app/profile/ivelasq.bsky.social" target="_blank" rel="noopener">Bluesky</a>.</p>









  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><p>Alfredo H. S. shared a small, opinionated skill for turning papers and other source material into concise Quarto RevealJS presentations with AI coding agents.</p>
<p>Try it out, and let Alfredo know how it goes!</p>
<ul>
<li><a href="https://forum.posit.co/t/a-small-quarto-ai-skill-for-better-slides/215878" target="_blank" rel="noopener">Forum discussion</a></li>
<li><a href="https://alfredohs.com/blog/quarto_talks/" target="_blank" rel="noopener">About the skill</a></li>
<li><a href="http://SKILL.md" target="_blank" rel="noopener">SKILL.md</a></li>
</ul>
</div>
    
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-08-05_glimpse-aug-2026/images/image1.png"
        alt="" 
        loading="lazy"
      >
    </figure></div></div>
    
  
</div>










  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-08-05_glimpse-aug-2026/images/image2.png"
        alt="" 
        loading="lazy"
      >
    </figure></div></div>
    
  
    
    
    
      <div class="prose max-w-none "><p>REYL Intesa Sanpaolo&rsquo;s Risk Management team built their own Incident Management Tool with Shiny and Posit Connect, cutting incident follow-up from days to 30 minutes. Now, they&rsquo;re eyeing the same R-and-Connect pattern for liquidity monitoring, credit risk appetite, and stress testing.</p>
<ul>
<li><a href="https://posit.co/about/customer-stories/reyl-intesa-sanpaolo" target="_blank" rel="noopener">Check out the spotlight</a></li>
</ul></div>
    
  
</div>










  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><p>Interested in testing your regression skills? Angela Heberger is looking for feedback on RelationLab, an interactive Shiny app for learning correlation and regression. Give it a shot and let Angela know what you think!</p>
<ul>
<li><a href="https://forum.posit.co/t/feedback-requested-on-relationlab-an-interactive-shiny-app-for-learning-correlation-and-regression/216954" target="_blank" rel="noopener">Forum discussion</a></li>
<li><a href="https://angela-heberger.shinyapps.io/correlation-regression-learning-app/" target="_blank" rel="noopener">Shiny app</a></li>
</ul></div>
    
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-08-05_glimpse-aug-2026/images/image3.png"
        alt="" 
        loading="lazy"
      >
    </figure></div></div>
    
  
</div>

<p>We usually find these projects on social media. If you&rsquo;re on LinkedIn, be sure to follow and tag <a href="https://www.linkedin.com/showcase/posit-open-source/" target="_blank" rel="noopener">Posit Open Source</a> for us to share the amazing things you&rsquo;re working on!</p>
<h2 id="whats-next">What’s next
</h2>
<p>Besides posit::conf (have you <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">registered for conf</a> yet? You should!), we have lots of goodies on the horizon.</p>
<ul>
<li>Emil Hvidtfelt is leading a 2-hour workshop all about making great slides in Quarto on Aug 12th. <a href="https://events.zoom.us/ev/AhS1wYPFlx9m2sfuhnidFjnvI2UlS3PKv6JRVL_bv4UUydDxeKnK~AtgzvV6vmUoaeLU-9KZrRyj3QUGklZmSV2a1hFRfixtmtlszjyaUoneXVw" target="_blank" rel="noopener">Register here</a>!</li>
<li>Join us for an upcoming <a href="https://pos.it/dsh" target="_blank" rel="noopener">Data Science Hangout</a> or <a href="https://pos.it/dslab" target="_blank" rel="noopener">Data Science Lab</a>.</li>
</ul>
<p>I would love to know how to make the Glimpse newsletter better. Email me at isabella [dot] velasquez [at] posit.co.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-08-05_glimpse-aug-2026/thumbnail.jpg" length="68574" type="image/jpeg" />
    </item>
    <item>
      <title>Shiny updates: R 1.14, Python 1.7, bslib 0.12</title>
      <link>https://opensource.posit.co/blog/2026-08-04_shiny-r-1-14-python-1-7/</link>
      <pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-08-04_shiny-r-1-14-python-1-7/</guid>
      <dc:creator>Barret Schloerke</dc:creator><description><![CDATA[<p>We&rsquo;re happy to announce a trio of releases: <a href="https://cran.r-project.org/package=shiny" target="_blank" rel="noopener">Shiny for R v1.14</a> and <a href="https://cran.r-project.org/package=bslib" target="_blank" rel="noopener">bslib v0.12</a> are now on CRAN, and <a href="https://pypi.org/project/shiny/" target="_blank" rel="noopener">Shiny for Python v1.7</a> is now on PyPI!</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">R</a></li>
<li><a href="#tabset-1-2">Python</a></li>
</ul>
<div id="tabset-1-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;shiny&#34;</span><span class="p">,</span> <span class="s">&#34;bslib&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
</div>
<div id="tabset-1-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install -U shiny</span></span></code></pre></div></div>
</div>
</div>
<p>The highlights: <a href="#agent-skills">bundled agent skills</a> that teach coding agents how to write Shiny for Python apps, <a href="#non-blocking-apps"><code>startApp()</code></a> for running R apps without blocking your console, <a href="#offcanvas-panels">offcanvas panels</a> that slide in from the edge of the viewport, and <a href="#module-cleanup"><code>destroy()</code></a> for cleaning up dynamic modules.</p>
<p>Full details are in the <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/upgrade.html" target="_blank" rel="noopener">Shiny for R release notes</a>, the <a href="https://rstudio.github.io/bslib/news/index.html#bslib-0120" target="_blank" rel="noopener">bslib release notes</a>, and the <a href="https://github.com/posit-dev/py-shiny/blob/main/CHANGELOG.md" target="_blank" rel="noopener">Shiny for Python changelog</a>.</p>
<h2 id="agent-skills">Agent skills
</h2>
<p><em>Shiny for Python only &mdash; coming to R soon.</em></p>
<p>Coding agents are writing more and more Shiny apps &mdash; so we&rsquo;re teaching them how to do it well. Shiny for Python v1.7 ships with bundled <a href="https://agentskills.io" target="_blank" rel="noopener">Agent Skills</a>: a <code>shiny-for-python</code> skill whose <code>SKILL.md</code> routes agents to focused reference files covering each area of Shiny&rsquo;s public API &mdash; reactivity, Express mode, modules, layouts, plots, data frames, chat and streaming, extended tasks, testing, debugging, OpenTelemetry, and more.</p>
<p>To install the bundled skills into your coding agent, use <a href="https://library-skills.io" target="_blank" rel="noopener">library-skills</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uvx library-skills --claude</span></span></code></pre></div></div>
<p>Because the skills ship inside the package, they always match the version of Shiny you have installed &mdash; and <code>shiny skills list</code> shows what&rsquo;s bundled.</p>
<p>Now your agent stops hand-rolling HTML tables and fake tabs, and starts using the framework the way you would.</p>
<h2 id="non-blocking-apps">Non-blocking apps
</h2>
<p><em>Shiny for R only.</em></p>
<p><a href="https://shiny.posit.co/r/reference/shiny/1.14.0/runApp.html" target="_blank" rel="noopener"><code>runApp()</code></a> blocks your R console until the app stops. New in Shiny v1.14, <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/startApp.html" target="_blank" rel="noopener"><code>startApp()</code></a> runs the app in the background and hands control right back to you:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Start app in the background</span>
</span></span><span class="line"><span class="cl"><span class="n">handle</span> <span class="o">&lt;-</span> <span class="nf">startApp</span><span class="p">(</span><span class="s">&#34;myapp&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The console remains available</span>
</span></span><span class="line"><span class="cl"><span class="n">handle</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;running&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">handle</span><span class="o">$</span><span class="nf">url</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;http://127.0.0.1:7365&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Stop the app</span>
</span></span><span class="line"><span class="cl"><span class="n">handle</span><span class="o">$</span><span class="nf">stop</span><span class="p">()</span></span></span></code></pre></div></div>
<p>The returned <code>ShinyAppHandle</code> has <code>stop()</code>, <code>status()</code>, <code>url()</code>, and <code>result()</code> methods. Starting a new app automatically stops the previous one, so iterating is as simple as calling <code>startApp()</code> again.</p>
<p>This is handy for interactive development, but it really shines for anything that needs to drive an app <em>and</em> keep working: testing tools, coding agents, or scripts that launch an app, interact with it, and shut it down.</p>
<p>That&rsquo;s the tooling around your app. The next two features are about the app itself &mdash; starting with a new way to keep UI off screen until someone asks for it.</p>
<h2 id="offcanvas-panels">Offcanvas panels
</h2>
<p>An offcanvas is a panel that slides in from an edge of the viewport &mdash; perfect for settings, filters, details-on-demand, or anything else that doesn&rsquo;t need to be on screen all the time. It&rsquo;s built on <a href="https://getbootstrap.com/docs/5.3/components/offcanvas/" target="_blank" rel="noopener">Bootstrap 5&rsquo;s offcanvas component</a> and comes with the full set of server verbs: <code>show_offcanvas()</code>, <code>hide_offcanvas()</code>, and <code>toggle_offcanvas()</code>.</p>
<p>The simplest way to use one is with a <code>trigger</code> element &mdash; no server code required:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">R</a></li>
<li><a href="#tabset-2-2">Python</a></li>
</ul>
<div id="tabset-2-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">offcanvas</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Panel content goes here.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Settings&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">trigger</span> <span class="o">=</span> <span class="nf">actionButton</span><span class="p">(</span><span class="s">&#34;open&#34;</span><span class="p">,</span> <span class="s">&#34;Open settings&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-2-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">offcanvas</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Panel content goes here.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Settings&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">trigger</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">input_action_button</span><span class="p">(</span><span class="s2">&#34;open&#34;</span><span class="p">,</span> <span class="s2">&#34;Open settings&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<figure>
<img src="https://opensource.posit.co/blog/2026-08-04_shiny-r-1-14-python-1-7/offcanvas-trigger.png" data-fig-alt="A Shiny app with an open offcanvas panel titled Settings sliding in from the right, dimming the page behind it" data-fig-align="center" alt="Clicking the trigger button reveals the offcanvas panel" />
<figcaption aria-hidden="true">Clicking the trigger button reveals the offcanvas panel</figcaption>
</figure>
<p>Give the panel an <code>id</code> and it becomes fully programmable: control it from the server and reactively respond to whether it&rsquo;s open.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">R</a></li>
<li><a href="#tabset-3-2">Python</a></li>
</ul>
<div id="tabset-3-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">actionButton</span><span class="p">(</span><span class="s">&#34;toggle&#34;</span><span class="p">,</span> <span class="s">&#34;Toggle details&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">offcanvas</span><span class="p">(</span><span class="s">&#34;Panel content&#34;</span><span class="p">,</span> <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Details&#34;</span><span class="p">,</span> <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;details&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">toggle</span><span class="p">,</span> <span class="nf">toggle_offcanvas</span><span class="p">(</span><span class="s">&#34;details&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">details</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">message</span><span class="p">(</span><span class="s">&#34;Panel is open: &#34;</span><span class="p">,</span> <span class="n">input</span><span class="o">$</span><span class="n">details</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-3-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">reactive</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="nb">input</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_action_button</span><span class="p">(</span><span class="s2">&#34;toggle&#34;</span><span class="p">,</span> <span class="s2">&#34;Toggle details&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">offcanvas</span><span class="p">(</span><span class="s2">&#34;Panel content&#34;</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Details&#34;</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;details&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">toggle</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">toggle_offcanvas</span><span class="p">(</span><span class="s2">&#34;details&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">state</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Panel is </span><span class="si">{</span><span class="s1">&#39;open&#39;</span> <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">details</span><span class="p">()</span> <span class="k">else</span> <span class="s1">&#39;closed&#39;</span><span class="si">}</span><span class="s2">&#34;</span></span></span></code></pre></div></div>
</div>
</div>
<p>Panels can slide in from the <code>left</code>, <code>right</code>, <code>top</code>, or <code>bottom</code>, and you can even build one entirely in the server and reveal it with <code>show_offcanvas()</code> &mdash; no UI placement needed.</p>
<p>Offcanvas panels are available now in Shiny for Python v1.7 and in <a href="https://rstudio.github.io/bslib/" target="_blank" rel="noopener">bslib</a> v0.12 for R. See the <code>offcanvas()</code> (<a href="https://rstudio.github.io/bslib/reference/offcanvas.html" target="_blank" rel="noopener">R</a>, <a href="https://shiny.posit.co/py/api/express/express.ui.offcanvas.html" target="_blank" rel="noopener">Python</a>) reference for the panel itself, and <code>show_offcanvas()</code>, <code>hide_offcanvas()</code>, and <code>toggle_offcanvas()</code> (<a href="https://rstudio.github.io/bslib/reference/show_offcanvas.html" target="_blank" rel="noopener">R</a>, <a href="https://shiny.posit.co/py/api/express/express.ui.show_offcanvas.html" target="_blank" rel="noopener">Python</a>) for controlling it from the server.</p>
<h2 id="module-cleanup">Module cleanup
</h2>
<p>An offcanvas hides UI that&rsquo;s already there. Creating and destroying UI on the fly is a different problem, and modules make it easy to <em>add</em> UI and server logic dynamically. Removing them has always been the awkward part: <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/insertUI.html" target="_blank" rel="noopener"><code>removeUI()</code></a> takes the HTML away, but the module&rsquo;s observers, reactive values, and outputs keep running behind the scenes &mdash; leaving behind &ldquo;dangling reactivity&rdquo;.</p>
<p>The session&rsquo;s new <code>destroy()</code> method (<a href="https://shiny.posit.co/r/reference/shiny/1.14.0/session.html" target="_blank" rel="noopener">R</a>, <a href="https://shiny.posit.co/py/api/core/Session.html" target="_blank" rel="noopener">Python</a>) closes that gap. The parent that inserted a module can now clean it up by the same id it used to insert it &mdash; no cleanup handles to pass around:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">R</a></li>
<li><a href="#tabset-4-2">Python</a></li>
</ul>
<div id="tabset-4-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># In the parent server</span>
</span></span><span class="line"><span class="cl"><span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">add</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">insertUI</span><span class="p">(</span><span class="s">&#34;#container&#34;</span><span class="p">,</span> <span class="n">ui</span> <span class="o">=</span> <span class="nf">myModuleUI</span><span class="p">(</span><span class="s">&#34;editor&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="nf">myModuleServer</span><span class="p">(</span><span class="s">&#34;editor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">remove</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">removeUI</span><span class="p">(</span><span class="n">selector</span> <span class="o">=</span> <span class="s">&#34;#editor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">session</span><span class="o">$</span><span class="nf">destroy</span><span class="p">(</span><span class="s">&#34;editor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span></span></span></code></pre></div></div>
</div>
<div id="tabset-4-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">add</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">insert_ui</span><span class="p">(</span><span class="n">my_module_ui</span><span class="p">(</span><span class="s2">&#34;editor&#34;</span><span class="p">),</span> <span class="n">selector</span><span class="o">=</span><span class="s2">&#34;#container&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">my_module_server</span><span class="p">(</span><span class="s2">&#34;editor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">remove</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">_</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">remove_ui</span><span class="p">(</span><span class="n">selector</span><span class="o">=</span><span class="s2">&#34;#editor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">destroy</span><span class="p">(</span><span class="s2">&#34;editor&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Destroying a scope invokes all of its registered <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/session.html" target="_blank" rel="noopener"><code>onDestroy()</code></a> (R) / <a href="https://shiny.posit.co/py/api/core/Session.html" target="_blank" rel="noopener"><code>on_destroy()</code></a> (Python) callbacks, cleaning up reactive values, expressions, observers, inputs, and outputs for that module <em>and</em> its descendant modules. Everything is scoped, so the parent session and sibling modules are untouched. And a module can call <code>destroy()</code> on its own session (no id) to clean up after itself.</p>
<p>If your app inserts and removes modules over a long-lived session, <code>destroy()</code> keeps those removed modules from accumulating as memory and reactivity leaks.</p>
<h2 id="test-mode">Test mode
</h2>
<p><em>Shiny for Python only.</em></p>
<p>Back to agents for a moment. The bundled skills teach them how to <em>write</em> your app; test mode, also new in v1.7, lets them &mdash; and your tests &mdash; <em>see inside</em> it while it runs. Enable it with the <code>SHINY_TESTMODE=1</code> environment variable (or <code>App(test_mode=True)</code>), and each session serves a live JSON snapshot of its <code>input</code>, <code>output</code>, and exported values.</p>
<p>The snapshot is only served when test mode is enabled, and by default it includes only inputs and outputs. To surface an internal reactive &mdash; a <code>reactive.calc</code> or <code>reactive.value</code> that never reaches the UI &mdash; export it with <a href="https://shiny.posit.co/py/api/core/testmode.export_test_values.html" target="_blank" rel="noopener"><code>export_test_values()</code></a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="nb">input</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.testmode</span> <span class="kn">import</span> <span class="n">export_test_values</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_slider</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;N&#34;</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">doubled</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">input</span><span class="o">.</span><span class="n">n</span><span class="p">()</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">txt</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;n * 2 = </span><span class="si">{</span><span class="n">doubled</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Surface the internal reactive in the test-mode snapshot. This is a no-op</span>
</span></span><span class="line"><span class="cl"><span class="c1"># unless test mode is enabled, so it&#39;s safe to leave in production code.</span>
</span></span><span class="line"><span class="cl"><span class="n">export_test_values</span><span class="p">(</span><span class="n">doubled</span><span class="o">=</span><span class="n">doubled</span><span class="p">)</span></span></span></code></pre></div></div>
<p>The pytest app fixtures (<code>local_app</code>, <code>create_app_fixture</code>) enable test mode automatically, and the new <a href="https://shiny.posit.co/py/api/testing/playwright.controller.AppTestValues.html" target="_blank" rel="noopener"><code>shiny.playwright.controller.AppTestValues</code></a> controller reads the snapshot in end-to-end tests. Expectations accept exact values or predicates:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">is_even</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">value</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_values</span> <span class="o">=</span> <span class="n">controller</span><span class="o">.</span><span class="n">AppTestValues</span><span class="p">(</span><span class="n">page</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">app_values</span><span class="o">.</span><span class="n">expect_export</span><span class="p">(</span><span class="s2">&#34;doubled&#34;</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">app_values</span><span class="o">.</span><span class="n">expect_export</span><span class="p">(</span><span class="s2">&#34;doubled&#34;</span><span class="p">,</span> <span class="n">is_even</span><span class="p">)</span></span></span></code></pre></div></div>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Scrubbing volatile values</span>
</div>
<div class="callout-body">
<p>Need to remove timestamps or temp paths before they are written to the snapshot? Register a preprocessor with <a href="https://shiny.posit.co/py/api/core/testmode.snapshot_preprocess_input.html" target="_blank" rel="noopener"><code>input.set_snapshot_preprocess()</code></a> or <code>my_output.snapshot_preprocess()</code>, and the snapshot stays stable from run to run.</p>
</div>
</div>
<p>Test mode mirrors R&rsquo;s long-standing <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/exportTestValues.html" target="_blank" rel="noopener"><code>exportTestValues()</code></a> &mdash; and it gives coding agents a structured way to debug a running app directly from the server instead of inferring information from the UI.</p>
<p>New to testing Shiny apps? Start with <a href="https://shiny.posit.co/py/docs/unit-testing.html" target="_blank" rel="noopener">Unit testing</a> and <a href="https://shiny.posit.co/py/docs/end-to-end-testing.html" target="_blank" rel="noopener">End-to-end testing</a> on the Shiny for Python website, then browse the <a href="https://shiny.posit.co/py/api/testing/index.html" target="_blank" rel="noopener">testing API reference</a>.</p>
<h2 id="other-improvements">Other improvements
</h2>
<p>A few more changes worth a quick mention &mdash; the full lists are in the release notes for <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/upgrade.html" target="_blank" rel="noopener">Shiny for R v1.14.0</a>, <a href="https://rstudio.github.io/bslib/news/index.html#bslib-0120" target="_blank" rel="noopener">bslib v0.12.0</a>, and <a href="https://github.com/posit-dev/py-shiny/blob/main/CHANGELOG.md" target="_blank" rel="noopener">Shiny for Python v1.7.0</a>:</p>
<h3 id="r">R
</h3>
<ul>
<li><a href="https://shiny.posit.co/r/reference/shiny/1.14.0/downloadButton.html" target="_blank" rel="noopener"><code>downloadButton()</code></a> and <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/downloadButton.html" target="_blank" rel="noopener"><code>downloadLink()</code></a> gain an <code>enabled</code> argument. The default, <code>&quot;auto&quot;</code>, automatically enables the button once the download is ready.</li>
<li>Output resize and visibility detection now uses native browser observers (<code>ResizeObserver</code>, <code>IntersectionObserver</code>), so plot sizing and hidden-state tracking work in any layout &mdash; including CSS-only show/hide and non-Bootstrap frameworks.</li>
<li><a href="https://shiny.posit.co/r/reference/shiny/1.14.0/conditionalPanel.html" target="_blank" rel="noopener"><code>conditionalPanel()</code></a> no longer briefly flashes its contents on app start when the condition is initially <code>FALSE</code>.</li>
<li>bslib v0.12 also lets you grab the sidebar handle directly to resize a sidebar, and fixes the resize handle indicator for <code>position = &quot;right&quot;</code> sidebars.</li>
</ul>
<h3 id="python">Python
</h3>
<ul>
<li><a href="https://shiny.posit.co/py/api/express/express.render.download_button.html" target="_blank" rel="noopener"><code>@render.download_button</code></a> and <a href="https://shiny.posit.co/py/api/express/express.render.download_link.html" target="_blank" rel="noopener"><code>@render.download_link</code></a> pair 1:1 with <code>ui.download_button()</code> and <code>ui.download_link()</code>, replacing the now-deprecated <code>@render.download</code>.</li>
<li>The <code>shiny[otel]</code> extra now sets up OpenTelemetry zero-code auto-instrumentation out of the box: <code>opentelemetry-instrument shiny run app.py</code>.</li>
</ul>
<h2 id="in-closing">In closing
</h2>
<p>We&rsquo;re excited to see what you build (and clean up) with these releases. As always, if you have questions or feedback, <a href="https://discord.gg/yMGCamUMnS" target="_blank" rel="noopener">join us on Discord</a> or open an issue on <a href="https://github.com/rstudio/shiny/issues/new" target="_blank" rel="noopener">rstudio/shiny</a> or <a href="https://github.com/posit-dev/py-shiny/issues/new" target="_blank" rel="noopener">posit-dev/py-shiny</a>. Happy Shiny-ing!</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to all the folks who helped make these releases happen:</p>
<h4 id="shiny-for-r-v1140">Shiny for R <a href="https://shiny.posit.co/r/reference/shiny/1.14.0/upgrade.html" target="_blank" rel="noopener">v1.14.0</a>
</h4>
<p><a href="https://github.com/adit-0132" target="_blank" rel="noopener">@adit-0132</a>, <a href="https://github.com/ahnungslos-git" target="_blank" rel="noopener">@ahnungslos-git</a>, <a href="https://github.com/byronvickers" target="_blank" rel="noopener">@byronvickers</a>, <a href="https://github.com/cpsievert" target="_blank" rel="noopener">@cpsievert</a>, <a href="https://github.com/cuckooland" target="_blank" rel="noopener">@cuckooland</a>, <a href="https://github.com/djacob65" target="_blank" rel="noopener">@djacob65</a>, <a href="https://github.com/dmurdoch" target="_blank" rel="noopener">@dmurdoch</a>, <a href="https://github.com/elnelson575" target="_blank" rel="noopener">@elnelson575</a>, <a href="https://github.com/FBrockmeyer" target="_blank" rel="noopener">@FBrockmeyer</a>, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>, <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>, <a href="https://github.com/IvanM26" target="_blank" rel="noopener">@IvanM26</a>, <a href="https://github.com/jcheng5" target="_blank" rel="noopener">@jcheng5</a>, <a href="https://github.com/jeffkeller-einc" target="_blank" rel="noopener">@jeffkeller-einc</a>, <a href="https://github.com/jeis4wpi" target="_blank" rel="noopener">@jeis4wpi</a>, <a href="https://github.com/JohnCoene" target="_blank" rel="noopener">@JohnCoene</a>, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>, <a href="https://github.com/karangattu" target="_blank" rel="noopener">@karangattu</a>, <a href="https://github.com/klin333" target="_blank" rel="noopener">@klin333</a>, <a href="https://github.com/lachlansimpson" target="_blank" rel="noopener">@lachlansimpson</a>, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>, <a href="https://github.com/marcosnav" target="_blank" rel="noopener">@marcosnav</a>, <a href="https://github.com/mconflitti-pbc" target="_blank" rel="noopener">@mconflitti-pbc</a>, <a href="https://github.com/ml-ebs-ext" target="_blank" rel="noopener">@ml-ebs-ext</a>, <a href="https://github.com/nbenn" target="_blank" rel="noopener">@nbenn</a>, <a href="https://github.com/Noskario" target="_blank" rel="noopener">@Noskario</a>, <a href="https://github.com/prinjos" target="_blank" rel="noopener">@prinjos</a>, <a href="https://github.com/pyltime" target="_blank" rel="noopener">@pyltime</a>, <a href="https://github.com/rikivillalba" target="_blank" rel="noopener">@rikivillalba</a>, <a href="https://github.com/roberson4627-cpu" target="_blank" rel="noopener">@roberson4627-cpu</a>, <a href="https://github.com/samuelbharti" target="_blank" rel="noopener">@samuelbharti</a>, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>, <a href="https://github.com/shikokuchuo" target="_blank" rel="noopener">@shikokuchuo</a>, <a href="https://github.com/simon-smart88" target="_blank" rel="noopener">@simon-smart88</a>, <a href="https://github.com/toph-allen" target="_blank" rel="noopener">@toph-allen</a>, and <a href="https://github.com/Upipa" target="_blank" rel="noopener">@Upipa</a>.</p>
<h4 id="bslib-v0120">bslib <a href="https://rstudio.github.io/bslib/news/index.html#bslib-0120" target="_blank" rel="noopener">v0.12.0</a>
</h4>
<p><a href="https://github.com/AleKoure" target="_blank" rel="noopener">@AleKoure</a>, <a href="https://github.com/ArthurAndrews" target="_blank" rel="noopener">@ArthurAndrews</a>, <a href="https://github.com/averissimo" target="_blank" rel="noopener">@averissimo</a>, <a href="https://github.com/elnelson575" target="_blank" rel="noopener">@elnelson575</a>, <a href="https://github.com/etiennebacher" target="_blank" rel="noopener">@etiennebacher</a>, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>, <a href="https://github.com/jeis4wpi" target="_blank" rel="noopener">@jeis4wpi</a>, <a href="https://github.com/LeonidasZhak" target="_blank" rel="noopener">@LeonidasZhak</a>, <a href="https://github.com/lgaborini" target="_blank" rel="noopener">@lgaborini</a>, <a href="https://github.com/lgschuck" target="_blank" rel="noopener">@lgschuck</a>, <a href="https://github.com/sawelch-NIVA" target="_blank" rel="noopener">@sawelch-NIVA</a>, <a href="https://github.com/sims1253" target="_blank" rel="noopener">@sims1253</a>, and <a href="https://github.com/willgearty" target="_blank" rel="noopener">@willgearty</a>.</p>
<h4 id="shiny-for-python-v170">Shiny for Python <a href="https://github.com/posit-dev/py-shiny/blob/main/CHANGELOG.md" target="_blank" rel="noopener">v1.7.0</a>
</h4>
<p><a href="https://github.com/chernojagne" target="_blank" rel="noopener">@chernojagne</a>, <a href="https://github.com/cpsievert" target="_blank" rel="noopener">@cpsievert</a>, <a href="https://github.com/eeshsaxena" target="_blank" rel="noopener">@eeshsaxena</a>, <a href="https://github.com/elnelson575" target="_blank" rel="noopener">@elnelson575</a>, <a href="https://github.com/EltonChang1" target="_blank" rel="noopener">@EltonChang1</a>, <a href="https://github.com/FBruzzesi" target="_blank" rel="noopener">@FBruzzesi</a>, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>, <a href="https://github.com/hutch3232" target="_blank" rel="noopener">@hutch3232</a>, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>, <a href="https://github.com/karangattu" target="_blank" rel="noopener">@karangattu</a>, <a href="https://github.com/kb071216" target="_blank" rel="noopener">@kb071216</a>, <a href="https://github.com/kbzsl" target="_blank" rel="noopener">@kbzsl</a>, <a href="https://github.com/marcosnav" target="_blank" rel="noopener">@marcosnav</a>, <a href="https://github.com/mariameraz" target="_blank" rel="noopener">@mariameraz</a>, <a href="https://github.com/MukundaKatta" target="_blank" rel="noopener">@MukundaKatta</a>, <a href="https://github.com/mvanhorn" target="_blank" rel="noopener">@mvanhorn</a>, <a href="https://github.com/nightcityblade" target="_blank" rel="noopener">@nightcityblade</a>, <a href="https://github.com/pevolution-ahmed" target="_blank" rel="noopener">@pevolution-ahmed</a>, <a href="https://github.com/QuintonBaker-USDA" target="_blank" rel="noopener">@QuintonBaker-USDA</a>, <a href="https://github.com/saisharan0103" target="_blank" rel="noopener">@saisharan0103</a>, <a href="https://github.com/SamEdwardes" target="_blank" rel="noopener">@SamEdwardes</a>, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>, <a href="https://github.com/slupczynskim" target="_blank" rel="noopener">@slupczynskim</a>, <a href="https://github.com/Steven314" target="_blank" rel="noopener">@Steven314</a>, and <a href="https://github.com/tomaioo" target="_blank" rel="noopener">@tomaioo</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-08-04_shiny-r-1-14-python-1-7/feature.gif" length="449496" type="image/gif" />
    </item>
    <item>
      <title>Quarto 1.10</title>
      <link>https://opensource.posit.co/blog/2026-08-03_quarto-1-10/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-08-03_quarto-1-10/</guid>
      <dc:creator>Charlotte Wickham</dc:creator><description><![CDATA[<p>Quarto 1.10 is out! You can get the current release from the <a href="https://quarto.org/docs/download/index.html" target="_blank" rel="noopener">download page</a>.</p>
<p>Quarto 1.10 is dominated by fixes, as much of our development effort is now going into <a href="https://opensource.posit.co/blog/2026-04-06_whats-next-quarto-2">Quarto 2</a>, but there are some improvements we wanted to point out: more refined HTML accessibility checks, and access to Quarto&rsquo;s localized strings for template authors.</p>
<p>You can read about these improvements and the most important fixes below. You can find all the changes in this version in the <a href="https://quarto.org/docs/download/changelog/1.10/" target="_blank" rel="noopener">Release Notes</a>.</p>
<h2 id="accessibility-checking-improvements">Accessibility Checking Improvements
</h2>
<p>Since Quarto 1.8, setting the <code>axe</code> option on HTML documents runs <a href="https://github.com/dequelabs/axe-core" target="_blank" rel="noopener">axe-core</a> accessibility checks on your rendered document. Quarto 1.10 makes these checks easier to rely on.</p>
<p><strong>Checking works offline.</strong> Quarto now bundles its own copy of axe-core instead of loading it from a CDN in the reader&rsquo;s browser. Accessibility checking works without a network connection, and viewing a rendered document no longer triggers a request to an external CDN. The axe-core version is unchanged, so results are identical.</p>
<p><strong>Check against a WCAG conformance level.</strong> The new <code>standard</code> option scopes checks to a WCAG conformance level, including rules axe-core keeps off by default for that level. The <code>best-practice</code> option controls whether axe&rsquo;s best-practice rules, recommendations not required by any WCAG success criterion, are also checked:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-0">
  <div class="code-with-filename-label" id="code-filename-0"><span class="font-mono text-sm">document.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">format</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">html</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">axe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">standard</span><span class="p">:</span><span class="w"> </span><span class="l">wcag21aa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">best-practice</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div></div>
<p><strong>A clearer report.</strong> When using <code>output: document</code>, violations are now listed first by impact, then WCAG conformance level. The report overlay also received its own accessibility and styling fixes: it can be scrolled with the keyboard, uses theme and brand independent colors, and no longer inherits page styling like text centering.</p>
<p>Learn more at <a href="https://quarto.org/docs/output-formats/html-accessibility.html" target="_blank" rel="noopener">HTML Accessibility</a>.</p>
<h2 id="localized-strings-in-templates">Localized Strings in Templates
</h2>
<p>If you write custom Pandoc templates or <a href="https://quarto.org/docs/journals/templates.html#template-partials" target="_blank" rel="noopener">template partials</a>, you can now use Quarto&rsquo;s localized strings through the new <code>quarto.language</code> template variable namespace. Quarto resolves each string for the document&rsquo;s language and exposes it as a template variable:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-1">
  <div class="code-with-filename-label" id="code-filename-1"><span class="font-mono text-sm">template.typ</span></div><pre tabindex="0"><code class="language-default" data-lang="default">$quarto.language.toc-title-document$
$quarto.language.crossref-fig-title$</code></pre></div>
<p>This lets templates honor the document&rsquo;s <code>lang</code> option without hard-coding text. For example, the <code>orange-book</code> Typst book extension now uses these variables in its running headers, so a book with <code>lang: fr</code> gets <em>Chapitre</em> rather than <em>Chapter</em>.</p>
<p>Learn more at <a href="https://quarto.org/docs/authoring/language.html#localized-strings-in-templates" target="_blank" rel="noopener">Localized Strings in Templates</a>.</p>
<h2 id="other-important-fixes">Other Important Fixes
</h2>
<ul>
<li>
<p><code>quarto preview</code> reliability&mdash;a long-standing bug that showed stale HTML for non-index pages is fixed, changes to a document&rsquo;s <code>format:</code> are detected on the first render after the edit, a <code>_brand.yml</code> added or removed during preview is picked up, and transient <code>.quarto_ipynb</code> files no longer accumulate on disk.</p>
</li>
<li>
<p><a href="https://quarto.org/docs/extensions/shortcodes.html" target="_blank" rel="noopener">Shortcodes</a>&mdash;shortcodes are now resolved inside inline and display math expressions.</p>
</li>
<li>
<p><a href="https://quarto.org/docs/output-formats/pdf-basics.html#fonts" target="_blank" rel="noopener">PDF font fallbacks</a>&mdash;<code>mainfontfallback</code>, <code>sansfontfallback</code>, and <code>monofontfallback</code> no longer crash LuaLaTeX on TeX Live 2026, and a missing fallback font is now installed automatically.</p>
</li>
<li>
<p>Typst fonts&mdash;fonts not installed locally are filtered from CSS <code>font-family</code> fallback lists before they reach Typst, suppressing <code>unknown font family</code> warnings, alongside a batch of fixes to Quarto&rsquo;s CSS-to-Typst translation.</p>
</li>
<li>
<p><a href="https://opensource.posit.co/blog/2026-04-14_chrome-headless-shell">Chrome Headless Shell</a>&mdash;the <code>quarto install chromium</code> command, deprecated in 1.9, now transparently redirects to <code>chrome-headless-shell</code>, <code>quarto check install</code> warns about legacy Chromium installs, and arm64 Linux is now supported.</p>
</li>
<li>
<p><a href="https://quarto.org/docs/websites/website-llms.html" target="_blank" rel="noopener">Output for LLMs</a>&mdash;fixes for websites with <code>llms-txt</code> enabled: headings and shortcodes inside conditional content, and clean-URL rewriting of <code>index.html.md</code> links.</p>
</li>
</ul>
<p>Dependency updates:</p>
<ul>
<li><code>pandoc</code> updated to 3.10</li>
<li><code>typst</code> updated to 0.15.1</li>
<li><code>deno</code> updated to 2.7.14</li>
<li><code>dart-sass</code> updated to 1.101.0</li>
<li><code>esbuild</code> updated to 0.28.1</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>We&rsquo;d like to say a huge thank you to everyone who contributed to this release by opening issues and pull requests:</p>
<p><a href="https://github.com/AlexLietard" target="_blank" rel="noopener">AlexLietard</a>,
<a href="https://github.com/ArthurRudolph" target="_blank" rel="noopener">ArthurRudolph</a>,
<a href="https://github.com/Artmann" target="_blank" rel="noopener">Artmann</a>,
<a href="https://github.com/C-Monaghan" target="_blank" rel="noopener">C-Monaghan</a>,
<a href="https://github.com/ChrisJefferson" target="_blank" rel="noopener">ChrisJefferson</a>,
<a href="https://github.com/CoryMcCartan" target="_blank" rel="noopener">CoryMcCartan</a>,
<a href="https://github.com/DCEW" target="_blank" rel="noopener">DCEW</a>,
<a href="https://github.com/DrFloLinke" target="_blank" rel="noopener">DrFloLinke</a>,
<a href="https://github.com/Guest-1013" target="_blank" rel="noopener">Guest-1013</a>,
<a href="https://github.com/MBe-iUS" target="_blank" rel="noopener">MBe-iUS</a>,
<a href="https://github.com/MurzNN" target="_blank" rel="noopener">MurzNN</a>,
<a href="https://github.com/ThierryO" target="_blank" rel="noopener">ThierryO</a>,
<a href="https://github.com/ThomasFaria" target="_blank" rel="noopener">ThomasFaria</a>,
<a href="https://github.com/TinasheMTapera" target="_blank" rel="noopener">TinasheMTapera</a>,
<a href="https://github.com/Voorhoeve" target="_blank" rel="noopener">Voorhoeve</a>,
<a href="https://github.com/alderete" target="_blank" rel="noopener">alderete</a>,
<a href="https://github.com/andrewheiss" target="_blank" rel="noopener">andrewheiss</a>,
<a href="https://github.com/barendgehrels" target="_blank" rel="noopener">barendgehrels</a>,
<a href="https://github.com/basm92" target="_blank" rel="noopener">basm92</a>,
<a href="https://github.com/blauzo" target="_blank" rel="noopener">blauzo</a>,
<a href="https://github.com/chainsawriot" target="_blank" rel="noopener">chainsawriot</a>,
<a href="https://github.com/crisbour" target="_blank" rel="noopener">crisbour</a>,
<a href="https://github.com/cs-res-pub-ser-st" target="_blank" rel="noopener">cs-res-pub-ser-st</a>,
<a href="https://github.com/dustinstoltz" target="_blank" rel="noopener">dustinstoltz</a>,
<a href="https://github.com/eculler" target="_blank" rel="noopener">eculler</a>,
<a href="https://github.com/eneveu" target="_blank" rel="noopener">eneveu</a>,
<a href="https://github.com/apps/github-actions" target="_blank" rel="noopener">github-actions[bot]</a>,
<a href="https://github.com/gregswinehart" target="_blank" rel="noopener">gregswinehart</a>,
<a href="https://github.com/hwine" target="_blank" rel="noopener">hwine</a>,
<a href="https://github.com/ianpittwood" target="_blank" rel="noopener">ianpittwood</a>,
<a href="https://github.com/ihrke" target="_blank" rel="noopener">ihrke</a>,
<a href="https://github.com/jdonaldson" target="_blank" rel="noopener">jdonaldson</a>,
<a href="https://github.com/jiangyun-fun" target="_blank" rel="noopener">jiangyun-fun</a>,
<a href="https://github.com/jidanni" target="_blank" rel="noopener">jidanni</a>,
<a href="https://github.com/jkrumbiegel" target="_blank" rel="noopener">jkrumbiegel</a>,
<a href="https://github.com/jnkatz" target="_blank" rel="noopener">jnkatz</a>,
<a href="https://github.com/jph00" target="_blank" rel="noopener">jph00</a>,
<a href="https://github.com/jtkulas" target="_blank" rel="noopener">jtkulas</a>,
<a href="https://github.com/juleswg23" target="_blank" rel="noopener">juleswg23</a>,
<a href="https://github.com/juliohm" target="_blank" rel="noopener">juliohm</a>,
<a href="https://github.com/kazuyanagimoto" target="_blank" rel="noopener">kazuyanagimoto</a>,
<a href="https://github.com/kelli-rstudio" target="_blank" rel="noopener">kelli-rstudio</a>,
<a href="https://github.com/lsbjordao" target="_blank" rel="noopener">lsbjordao</a>,
<a href="https://github.com/luismmontilla" target="_blank" rel="noopener">luismmontilla</a>,
<a href="https://github.com/maelle" target="_blank" rel="noopener">maelle</a>,
<a href="https://github.com/maucejo" target="_blank" rel="noopener">maucejo</a>,
<a href="https://github.com/memeplex" target="_blank" rel="noopener">memeplex</a>,
<a href="https://github.com/micedre" target="_blank" rel="noopener">micedre</a>,
<a href="https://github.com/multimeric" target="_blank" rel="noopener">multimeric</a>,
<a href="https://github.com/nathant181" target="_blank" rel="noopener">nathant181</a>,
<a href="https://github.com/neilernst" target="_blank" rel="noopener">neilernst</a>,
<a href="https://github.com/nessan" target="_blank" rel="noopener">nessan</a>,
<a href="https://github.com/nickvigilante" target="_blank" rel="noopener">nickvigilante</a>,
<a href="https://github.com/nrennie" target="_blank" rel="noopener">nrennie</a>,
<a href="https://github.com/pbosetti" target="_blank" rel="noopener">pbosetti</a>,
<a href="https://github.com/reckoner" target="_blank" rel="noopener">reckoner</a>,
<a href="https://github.com/rgouveiamendes" target="_blank" rel="noopener">rgouveiamendes</a>,
<a href="https://github.com/robjhyndman" target="_blank" rel="noopener">robjhyndman</a>,
<a href="https://github.com/sbwiecko" target="_blank" rel="noopener">sbwiecko</a>,
<a href="https://github.com/seandavi" target="_blank" rel="noopener">seandavi</a>,
<a href="https://github.com/sebastiansauer" target="_blank" rel="noopener">sebastiansauer</a>,
<a href="https://github.com/skyfroger" target="_blank" rel="noopener">skyfroger</a>,
<a href="https://github.com/songwupei" target="_blank" rel="noopener">songwupei</a>,
<a href="https://github.com/stefkuypers" target="_blank" rel="noopener">stefkuypers</a>,
<a href="https://github.com/stragu" target="_blank" rel="noopener">stragu</a>,
<a href="https://github.com/tiagojct" target="_blank" rel="noopener">tiagojct</a>,
<a href="https://github.com/victorrssx" target="_blank" rel="noopener">victorrssx</a>,
<a href="https://github.com/widlarizer" target="_blank" rel="noopener">widlarizer</a>,
<a href="https://github.com/wlatendresse" target="_blank" rel="noopener">wlatendresse</a>,
<a href="https://github.com/xuefeng-xu" target="_blank" rel="noopener">xuefeng-xu</a>,
<a href="https://github.com/yasyf" target="_blank" rel="noopener">yasyf</a>,
<a href="https://github.com/zinc75" target="_blank" rel="noopener">zinc75</a>.</p>
<p>The hammer and wrench emoji in the <a href="thumbnail.png">listing and social card image</a> for this post comes from <a href="https://openmoji.org/" class="external">OpenMoji</a>&ndash; the open-source emoji and icon project. License: <a href="https://creativecommons.org/licenses/by-sa/4.0/#" class="external">CC BY-SA 4.0</a></p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-08-03_quarto-1-10/thumbnail.png" length="48234" type="image/png" />
    </item>
    <item>
      <title>Positron&#39;s Jupyter Notebook Editor Is Now Generally Available</title>
      <link>https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/</link>
      <pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/</guid>
      <dc:creator>Wasim Lorgat</dc:creator>
      <dc:creator>Cindy Tong</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Data scientists are often forced to choose between the simplicity of the classic Jupyter notebook experience and the advanced capabilities of a general-purpose IDE. With the <a href="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/" target="_blank" rel="noopener">2026.07 release</a>, you no longer have to choose: Positron now delivers a first-class Notebook Editor inside an IDE built for data science.</p>
<p>If you&rsquo;re new to Positron, here&rsquo;s what the Notebook Editor is like in your daily work.</p>
<h2 id="environment-management">Environment management
</h2>
<p>You clone your colleague&rsquo;s repo and try to rerun their notebook. Which Python version do you use? What dependencies do you install and which versions? Did you previously set up an environment that you could reuse? You&rsquo;re faced with all of these questions before you&rsquo;ve run a single line of code.</p>
<p>Positron has several <a href="https://opensource.posit.co/blog/2026-07-08_positron-uv/" target="_blank" rel="noopener">built-in workflows</a> to help you manage your environments. When you open a repo, Positron discovers your installed environments, suggests the right one for the repo, and prompts you to set up a new environment if needed. The active environment is displayed front and center; click to restart or switch environments. Positron uses the same environment across your Jupyter and Quarto notebooks, scripts, and consoles. You can see inside your environments, which packages are installed, their versions, whether newer versions are available, and you can upgrade them. Environments are first-class citizens of Positron. <a href="https://positron.posit.co/positron-notebook-editor.html#setting-up-your-environment" target="_blank" rel="noopener">Explore Positron&rsquo;s environment management</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-from-git.gif" data-fig-align="center" data-fig-alt="Positron helps you resolve environment and package dependencies as you pull down a colleague&#39;s notebook" />
<h2 id="interactive-data-exploration">Interactive data exploration
</h2>
<p>When you run a cell in your notebook, your variables appear in the <a href="https://positron.posit.co/variables-pane.html" target="_blank" rel="noopener">Variables Pane</a>. Filter, sort, and search your data in the <a href="https://positron.posit.co/data-explorer.html" target="_blank" rel="noopener">Data Explorer</a>, and try out plot variations in the Visualize wizard. You can easily convert your point-and-click interactions into code by using the copy-code feature to bring them back into your notebook. If you get stuck, look up library documentation in the <a href="https://positron.posit.co/help-pane.html" target="_blank" rel="noopener">Help Pane</a>. All of this works out of the box.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-data-exploration.gif" data-fig-align="center" data-fig-alt="Run a notebook and see your variables update live, inspect them in the data explorer and convert filters back to code." />
<h2 id="streamlined-version-control">Streamlined version control
</h2>
<p>Notebooks store outputs and execution metadata alongside code, which is great for sharing but does not work well with version control. Positron includes settings to exclude outputs and execution metadata from the saved file, so the diff is just your code change. <a href="https://positron.posit.co/positron-notebook-editor.html#version-control" target="_blank" rel="noopener">Explore the custom settings</a> to clean up your git diffs.</p>
<p>Your notebooks stay ordinary <code>.ipynb</code> files and the editing experience stays classic Jupyter. When a plain-text workflow suits you better, conversion to and from <code>.ipynb</code> and other formats is built in.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-save-settings.png" data-fig-align="center" data-fig-alt="Customize what is saved in your notebooks for better version control." />
<h2 id="ai-assistance">AI assistance
</h2>
<p>Posit Assistant sees your notebook as more than just text. It works with the live session behind the notebook: your variables, your data, your plots. Ask it to fix a chart and it can inspect the dataframe, look at the plot itself, edit the cell, and run it again. It does the same things you would do, using the same panes you use. <a href="https://positron.posit.co/positron-notebook-editor.html#ai-integration" target="_blank" rel="noopener">Explore notebook-aware AI assistance in Positron</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-ai.gif" data-fig-align="center" data-fig-alt="Assistant can fix, explain, run your notebook and suggest next steps." />
<h2 id="get-started">Get started
</h2>
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> and open a project with a Jupyter notebook. The Notebook Editor is now on by default. If you&rsquo;re coming from another IDE, your existing <code>.ipynb</code> files will open as-is without any conversion needed.</p>
<p>For the full feature reference, check out our <a href="https://positron.posit.co" target="_blank" rel="noopener">documentation</a>.</p>
<p>If you run into any issues or have ideas on how we can improve Positron, reach out on <a href="https://github.com/posit-dev/positron" target="_blank" rel="noopener">GitHub</a>.</p>
<p>Already using Positron? Everything in this release is in the <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">release notes</a>.</p>
<p>Thank you so much for helping us build Positron&rsquo;s Jupyter notebook support from the ground up by chatting with us live, sharing your pain points, and giving us feedback on GitHub!</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the Notebook Editor and other new features!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/featured.png" length="686743" type="image/png" />
    </item>
    <item>
      <title>ir 0.1.0: self-describing R scripts and Quarto documents</title>
      <link>https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/</link>
      <pubDate>Thu, 23 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/</guid>
      <dc:creator>Tomasz Kalinowski</dc:creator>
      <dc:creator>Christophe Dervieux</dc:creator>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<style>
.chroma code[data-lang="r"] .c1 {
  font-style: normal;
}
</style>
<p>Today we are announcing the first public release of <code>ir</code>, a small
command-line tool that runs R scripts and renders Quarto documents using
runtime requirements declared in the file itself.</p>
<p><code>ir</code> is for one-file workflows that do not quite need a project but
still need to be easy to share and rerun later. Simply put the package
requirements, and optionally the R version, next to the code. When you
render or execute the file, <code>ir</code> resolves the requirements, prepares
cached package libraries, and launches R or Quarto with a runtime ready
to use.</p>
<p>The interface is inspired by
<a href="https://peps.python.org/pep-0723/" target="_blank" rel="noopener">PEP 723</a> and
<a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"><code>uv run --script</code></a>. A script can carry
enough metadata to describe its runtime, and the runner can resolve that
runtime into a cached environment on demand. <code>ir</code> brings that pattern to
R scripts and Quarto documents, using <code>pak</code>, <code>renv</code>, and <code>rig</code> on the R
side, and <code>reticulate</code>&rsquo;s uv-backed helper when Python is part of the
runtime.</p>
<p><code>ir</code> focuses on two workflows:</p>
<ul>
<li>running or rendering self-describing scripts and documents <br>
(<code>ir run</code>, <code>ir render</code>)</li>
<li>running or installing command-line tools distributed through R
packages <br>
(<code>rx</code>, <code>ir tool install</code>)</li>
</ul>
<h2 id="why-ir">Why <code>ir</code>?
</h2>
<p>R scripts often begin as small, local utilities: a report, a data pull,
a model-training or evaluation run, a quick diagnostic, or an example
shared with a colleague. Over time, the script can become important, but
the setup still lives somewhere else: in a README, in a shell history,
in a project library, or in the author&rsquo;s current R installation.</p>
<p><code>ir</code> makes the runtime specification part of the source file. That means
a script can say, directly:</p>
<ul>
<li>which packages it needs</li>
<li>which R should run it, when that needs to be explicit</li>
<li>whether user libraries should be visible</li>
<li>whether CRAN packages should be resolved as of a specific date</li>
</ul>
<p>This should help you rerun the script reliably at a later date. Keeping
the metadata in the file makes it less likely to be lost or fall out of
sync with the code.</p>
<h2 id="how-ir-fits-with-existing-tools">How <code>ir</code> fits with existing tools
</h2>
<p><code>ir</code> sits alongside the R tools people already use and builds on several
of them directly:</p>
<ul>
<li><a href="https://github.com/r-lib/rig" target="_blank" rel="noopener"><code>rig</code></a> installs, removes, and switches
between R versions on macOS, Windows, and Linux. <code>ir</code> calls <code>rig</code> when
a file requests a specific R version, or when date-only
<code>exclude-newer</code> needs to select the latest R minor version available
on that date. <code>rig</code> is optional for files that only declare packages.</li>
<li><a href="https://pak.r-lib.org/" target="_blank" rel="noopener"><code>pak</code></a> is a fast package installer with a
built-in solver. <code>ir</code> uses <code>pak</code> to resolve the dependency graph from
a file&rsquo;s declared packages and fetch them from the appropriate
repositories. <code>pak</code> is bootstrapped automatically on first use, so you
do not need to install it separately.</li>
<li><a href="https://rstudio.github.io/renv/" target="_blank" rel="noopener"><code>renv</code></a> gives R projects isolated
package libraries and lockfiles. <code>ir</code> uses <code>renv</code>&rsquo;s global package
cache to assemble reusable libraries without creating a <code>renv</code> project
or lockfile.</li>
</ul>
<p><code>ir</code> builds on that stack for non-project workflows: resolving the
runtime for a self-describing script or Quarto document, and running or
installing command-line entry points distributed by R packages.</p>
<h2 id="a-self-describing-r-script">A self-describing R script
</h2>
<p>Here is a complete script:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env -S ir run</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| packages:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - dplyr&gt;=1.0</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - tidyr</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| isolated: true</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| exclude-newer: &#34;2024-01-15&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">mtcars</span> <span class="o">|&gt;</span> <span class="nf">count</span><span class="p">(</span><span class="n">cyl</span><span class="p">,</span> <span class="n">gear</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">pivot_wider</span><span class="p">(</span><span class="n">names_from</span> <span class="o">=</span> <span class="n">gear</span><span class="p">,</span> <span class="n">values_from</span> <span class="o">=</span> <span class="n">n</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Run it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ir run script.R
</span></span></code></pre></div></div>
<p>Or, on macOS and Linux, make it executable and run it directly:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> chmod +x script.R
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ./script.R
</span></span></code></pre></div></div>
<p>The metadata block is YAML written in <code>#|</code> comments after an optional
shebang. <code>ir</code> reads that metadata, resolves the declared packages with
<code>pak</code>, materializes a package library with <code>renv</code>, and starts R with the
resolved library at the front of <code>.libPaths()</code>.</p>
<p>By default, user libraries remain visible as a fallback. Add
<code>isolated: true</code> in the file, or use <code>--isolated</code> at the command line,
to run without the user library.</p>
<h2 id="running-r-package-tools-with-rx">Running R package tools with <code>rx</code>
</h2>
<p>The <code>ir</code> release also includes <code>rx</code>, a short alias for <code>ir tool run</code>
that runs executables provided by R packages.</p>
<p>Package authors can expose command-line entry points through standard
package subdirectories such as <a href="https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Package-subdirectories-1" target="_blank" rel="noopener"><code>exec/</code> and <code>bin/</code></a>. Files in <code>exec/</code> can be regular <code>Rscript</code> files,
<a href="https://github.com/r-lib/Rapp" target="_blank" rel="noopener"><code>Rapp</code></a> apps, or direct executable
scripts. <code>rx</code> resolves the package, finds the requested executable, and
runs it in an isolated library.</p>
<p>For example, this resolves the <code>btw</code> CLI from the <code>btw</code> R package on
demand and runs <code>btw --help</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> rx btw --help
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> same as:
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ir tool run btw --help
</span></span></code></pre></div></div>
<p>For tools you use regularly, install them with <code>ir tool install</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> run once
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ir tool install btw
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> now you only need
</span></span><span class="line"><span class="cl"><span class="gp">$</span> btw --help
</span></span></code></pre></div></div>
<h2 id="cached-by-design">Cached by design
</h2>
<p>The first run of a new dependency set does the normal work of resolving
and installing packages. Later runs reuse cached resolutions and
content-addressed package libraries when the same requirements are seen
again.</p>
<p>That makes <code>ir</code> useful for both one-off and repeated command-line work.
You can run a script, run an inline expression, render a report, or
launch a package-provided executable without creating a project
directory just to hold the dependency state.</p>
<p><code>ir</code> also bootstraps its own resolver tooling on first use, so you do
not need to pre-install <code>pak</code> or <code>renv</code>.</p>
<h2 id="reproducibility-without-a-project">Reproducibility without a project
</h2>
<p>For R scripts that need more explicit reproducibility, <code>exclude-newer</code>
is usually the first thing to reach for:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">exclude-newer</span><span class="p">:</span><span class="w"> </span><span class="ld">2024-01-15</span></span></span></code></pre></div></div>
<p>You can also provide the same date at the command line:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ir run --exclude-newer 2024-01-15 script.R
</span></span></code></pre></div></div>
<p>This resolves packages from the Posit Package Manager snapshot for that
date. When no other R selector is set, the same date also tells <code>ir</code> to
select the latest R minor version available on that date. If you were
writing with the current release of R and current CRAN packages, the
date alone is usually enough.</p>
<p>Use <code>r-version</code> when the script really needs a specific installed R
version or version range. This selection uses
<a href="https://github.com/r-lib/rig" target="_blank" rel="noopener"><code>rig</code></a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">r-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4.3&#34;</span></span></span></code></pre></div></div>
<p>Together, these options let a file carry the important parts of its
runtime requirements without needing a surrounding project. For
reproducibility, most files should need only a list of packages and a
date.</p>
<h2 id="quarto-documents-too">Quarto documents too
</h2>
<p><code>ir</code> uses the same metadata model for Quarto documents. Put package
metadata under an <code>ir:</code> key in the document YAML:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">My report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ir</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dplyr&gt;=1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gt@1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">isolated</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">exclude-newer</span><span class="p">:</span><span class="w"> </span><span class="ld">2025-05-15</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>Then render with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ir render report.qmd
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ir render report.qmd --to pdf
</span></span></code></pre></div></div>
<p>When <code>ir</code> selects an R executable using <code>--r-version</code>, frontmatter
<code>ir.r-version</code>, or date-only <code>exclude-newer</code>, it sets <a href="https://quarto.org/docs/advanced/environment-vars.html#variables-quarto-inspects" target="_blank" rel="noopener"><code>QUARTO_R</code></a> so Quarto renders with that R. <code>ir</code> also seeds <code>rmarkdown</code>
automatically for knitr-based renders unless you declare it yourself.</p>
<h2 id="python-environments-too">Python environments too
</h2>
<p>Some R scripts and Quarto documents also need Python. In an R script,
declare the R packages and Python requirements in the same metadata
block:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env -S ir run</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| packages:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - reticulate</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| python-packages:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - pandas</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - matplotlib</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| python-version: &#34;3.11&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| exclude-newer: &#34;2026-06-01&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">reticulate</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">pd</span> <span class="o">&lt;-</span> <span class="nf">import</span><span class="p">(</span><span class="s">&#34;pandas&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><code>ir</code> creates the environment with <code>reticulate</code>&rsquo;s uv-backed environment
helper, sets <code>RETICULATE_PYTHON</code>, and activates the environment for
subprocesses. Declare <code>reticulate</code> under <code>packages</code> when the R script
loads <code>reticulate</code>.</p>
<p>For Quarto, put Python metadata under the document&rsquo;s <code>ir:</code> key. A knitr
document that uses reticulate can mix R and Python requirements:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">My report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ir</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">reticulate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">python-packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pandas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">exclude-newer</span><span class="p">:</span><span class="w"> </span><span class="ld">2025-01-01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>For a Quarto document that uses the Jupyter engine, the <code>ir:</code> metadata
can contain only Python requirements:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">My notebook</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jupyter</span><span class="p">:</span><span class="w"> </span><span class="l">python3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ir</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">python-packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">matplotlib</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pandas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.11&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>For Quarto renders, <code>ir</code> injects <code>jupyter</code> into the Python environment,
passes the resolved interpreter to Quarto with <code>QUARTO_PYTHON</code>, and also
sets <code>RETICULATE_PYTHON</code> so documents that use reticulate see the same
interpreter. If the document uses the knitr engine and contains Python
chunks, <code>ir</code> automatically adds <code>reticulate</code> to the R package manifest.</p>
<p>When Python metadata is present, <code>exclude-newer</code> is also used for Python
environment resolution unless <code>python-exclude-newer</code> is set. Use
<code>python-exclude-newer</code> when Python packages should use a different
snapshot date from R packages.</p>
<h2 id="install-ir">Install <code>ir</code>
</h2>
<p>Install a pre-built binary on Linux or macOS:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> curl -fsSL https://raw.githubusercontent.com/r-lib/ir/main/scripts/install.sh <span class="p">|</span> sh
</span></span></code></pre></div></div>
<p>Install on Windows PowerShell:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">&gt;</span> irm https://raw.githubusercontent.com/r-lib/ir/main/scripts/install.ps1 <span class="p">|</span> iex
</span></span></code></pre></div></div>
<p>The installers download the latest GitHub release and install both <code>ir</code>
and <code>rx</code>. You can also download release artifacts from the
<a href="https://github.com/r-lib/ir/releases" target="_blank" rel="noopener">GitHub releases</a> page or with
<code>gh release download</code>.</p>
<p>You will also need <code>R</code> / <code>Rscript</code>; <code>rig</code> is required when selecting R
by version or by date-only <code>exclude-newer</code>, and Quarto is required when
rendering Quarto sources.</p>
<h2 id="learn-more">Learn more
</h2>
<p>The project is open source under the MIT license. To get started:</p>
<ul>
<li>Read the documentation: <a href="https://r-lib.github.io/ir/" target="_blank" rel="noopener">https://r-lib.github.io/ir/</a></li>
<li>Browse the source: <a href="https://github.com/r-lib/ir" target="_blank" rel="noopener">https://github.com/r-lib/ir</a></li>
<li>Open an issue: <a href="https://github.com/r-lib/ir/issues" target="_blank" rel="noopener">https://github.com/r-lib/ir/issues</a></li>
</ul>
<p>This is a first public release, and feedback is especially useful now.
If you try <code>ir</code> on your own scripts or Quarto documents, we would like
to hear which workflows feel natural, where the metadata model needs
more room, and which command-line edges still need smoothing.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/terrarium.png" length="2120322" type="image/png" />
    </item>
    <item>
      <title>Tips for managing your Python &amp; R environments in Positron</title>
      <link>https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/</link>
      <pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/</guid>
      <dc:creator>Cindy Tong</dc:creator>
      <dc:creator>Brice Stacey</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is the Posit next-generation IDE for data science. Positron is an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>If you work across Python and R or juggle multiple projects with different dependency needs, environment management is probably one of the most challenging parts of your workflow. <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a> comes with several out-of-the-box ways to help simplify environment management. Here are a few tips to streamline your workflow.</p>
<h2 id="1-understand-how-positron-discovers-your-environment">1: Understand how Positron discovers your environment
</h2>
<p>Positron does not just look at your system PATH. For Python, it actively discovers venv, uv, pyenv, and conda environments. If Positron is not seeing your project&rsquo;s virtual environment, you can add custom search locations via the <a href="positron://settings/python.interpreters.include"><code>python.interpreters.include</code></a> setting, or trigger a manual rescan with <em>Interpreter: Discover All Interpreters</em>. Learn more about <a href="https://positron.posit.co/python-installations.html#python-installation-discovery" target="_blank" rel="noopener">Python discovery in the Positron documentation</a>.</p>
<p>For R, discovery works differently. Positron consults various sources to build the list of R interpreters. These include your PATH, R root folders based on specific operating systems, well-known executable locations, and on Windows the registry. You can customize your R discovery through a few settings including <a href="positron://settings/positron.r.customRootFolders"><code>positron.r.customRootFolders</code></a> and <a href="positron://settings/positron.r.customBinaries"><code>positron.r.customBinaries</code></a>. Learn more about <a href="https://positron.posit.co/r-installations.html#customizing-r-discovery" target="_blank" rel="noopener">R discovery in the Positron documentation</a>.</p>
<h2 id="2-use-the-interpreter-selector">2. Use the Interpreter Selector
</h2>
<p>To begin your first session, click &ldquo;Start Session&rdquo; in the top right corner and select your preferred R or Python interpreter. Positron can run multiple R and Python interpreter sessions at once, but only one is ever the active session at a given moment. The Interpreter Selector always shows you the active session and its status (idle, busy, or shut down) and you can use it to switch between or start additional sessions.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/active-interpreter-session.png" width="100%" data-fig-align="center" data-fig-alt="Use the Interpreter Selector to change your active session" />
<h2 id="3-install-python-with-uv">3. Install Python with uv
</h2>
<p>If Positron does not find a usable Python on your machine, it will offer to install Python via uv to help streamline your setup. If you prefer to manage the installation yourself, you can disable uv with the <a href="positron://settings/python.allowUvPythonInstall"><code>python.allowUvPythonInstall</code></a> setting. Check out our <a href="https://opensource.posit.co/blog/2026-07-08_positron-uv/" target="_blank" rel="noopener">blog post exploring on-demand Python installation in Positron</a> or explore configurations in our <a href="https://positron.posit.co/python-installations.html#troubleshooting" target="_blank" rel="noopener">Python installation documentation</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/install-python-with-uv.png" width="100%" data-fig-align="center" data-fig-alt="Install Python via uv" />
<h2 id="4-manage-packages-with-the-packages-pane">4. Manage packages with the Packages Pane
</h2>
<p>The Packages Pane in Positron lets you manage the packages installed in your active session. Whether you use pip, uv, conda, pak, base R, or renv, you can browse installed packages and search package repositories. You can also track outdated packages and install, update, or uninstall packages without leaving Positron or writing any code.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/packages-pane.gif" width="100%"  data-fig-align="center" data-fig-alt="Manage Python and R packages in the Packages Pane" />
<h2 id="5-view-package-documentation">5. View package documentation
</h2>
<p>If you need to learn more about a specific package, the Packages Pane has buttons to navigate to the source documentation or link to the package&rsquo;s website. You can also pull up the <a href="https://positron.posit.co/help-pane.html" target="_blank" rel="noopener">Help Pane</a> for any reference in the Console using the <code>?</code> operator, including both packages and functions.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/packages-pane-help.png" width="100%" data-fig-align="center" data-fig-alt="View source documentation for specific packages in the Help Pane" />
<h2 id="6-start-projects-with-new-folder-from-template">6. Start projects with New Folder from Template
</h2>
<p>The New Folder from Template flow helps you start new projects faster. Instead of running multiple setup commands you can make a few selections and Positron helps you set up an environment directory, version control, directory structure, and an interpreter instance. Learn more about the <a href="https://positron.posit.co/folder-templates.html" target="_blank" rel="noopener">Python and R templates</a> available in the documentation.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/new-folder-from-template.png" width="100%" data-fig-align="center" data-fig-alt="New Folder from Template helps you start new projects faster with ready to use environments" />
<h2 id="7-make-use-of-the-command-palette">7. Make use of the Command Palette
</h2>
<p>We recommend taking advantage of the following commands when managing your environments:</p>
<ul>
<li><em>Interpreter: Discover All Interpreters</em>: Find new interpreters</li>
<li><em>Interpreter: Select Session</em>: Select a running interpreter session</li>
<li><em>Python: Create Environment</em>: Create a new virtual environment</li>
</ul>
<h2 id="8-let-posit-assistant-help-manage-your-environment">8. Let Posit Assistant help manage your environment
</h2>
<p><a href="https://assistant.posit.co/docs/features/context-management/" target="_blank" rel="noopener">Posit Assistant</a> has knowledge of your R and Python session including the language, version, names, and types of variables in your environment. You can prompt Posit Assistant to set up environments and troubleshoot issues you run into.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/assistant-installed-packages.png" width="100%" data-fig-align="center" data-fig-alt="Posit Assistant in Positron showing a summary of packages installed in the user's Python environment, organized by category." />
<p>Have an idea for how we can improve environment management in Positron? We would love to hear from you in a <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">discussion on GitHub</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/featured.svg" length="75593" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>Positron July Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/</link>
      <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/" target="_blank" rel="noopener">Last release</a> we told you that several major features were on track to leave preview in July, and the time is now here! The new Notebook editor, the Packages pane, and Posit Assistant are all now generally available.</p>
<h2 id="positron-notebook-editor">Positron Notebook Editor
</h2>
<p>Positron&rsquo;s <a href="https://positron.posit.co/positron-notebook-editor" target="_blank" rel="noopener">new Notebook editor</a> is now the default experience for Jupyter (<code>.ipynb</code>) files. This release brings a long list of additions, including split-pane editing, cell tag management, and executing a line or selection within a cell with <kbd>Cmd/Ctrl+Shift+Enter</kbd>. You can export notebooks to Quarto, Python, or R, and inline PDF rendering makes it easier to work with generated output. If you are coming from JupyterLab, you will find familiar keyboard shortcuts, and we have improved output fidelity for Mermaid diagrams, htmlwidgets, and ipywidgets.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/positron-notebook-editor.png" data-fig-align="center" data-fig-alt="The Notebook editor in Positron, showing a Jupyter notebook with a Mermaid diagram." />
<p>Posit has built on and for the Jupyter ecosystem for years now, and we are excited to have recently <a href="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/" target="_blank" rel="noopener">joined the Jupyter Foundation</a>.</p>
<h2 id="packages-pane">Packages pane
</h2>
<p>The <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages pane</a> has also come out of preview. It gives you a live view of the R and Python packages installed in your active session, so you can search, install, update, and remove packages, and jump to their documentation, without leaving Positron.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/packages-pane.gif" data-fig-align="center" data-fig-alt="The Packages pane in Positron, showing installed Python packages with a detail editor." />
<p>This release also makes the pane more capable. Clicking a package opens a detail editor with its overview, metadata, and actions, similar to the Extensions pane. Installing a package searches results as you type, and each row has a button that opens the package&rsquo;s website. Update indicators appear immediately on a new session from a cached snapshot, and <strong>Update All Packages</strong> reports exactly what changed. Package operations keep your environment consistent too, resolving against a workspace <code>requirements.txt</code> for Python and updating the <code>renv.lock</code> snapshot for R.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p><a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a>, our unified, data-science-focused approach to AI assistance, is now out of preview and generally available; Posit Assistant fully replaces the legacy Positron Assistant. We know the names are similar, and Joe Cheng <a href="https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/" target="_blank" rel="noopener">recently walked through a bit of the story behind how our AI agent tools have evolved</a>.</p>
<p>This release also gives you finer control over AI in Positron. The new <a href="positron://settings/ai.enabled"><code>ai.enabled</code></a> setting turns off every Positron AI feature at once, and administrators can enforce it; <a href="positron://settings/notebook.ai.enabled"><code>notebook.ai.enabled</code></a> does the same for notebooks specifically. The set of language model providers keeps growing, with DeepSeek joining as an experimental provider and Microsoft Foundry reaching general availability. The configuration modal now shows every provider by default with preview and experimental badges.</p>
<h2 id="data-explorer">Data Explorer
</h2>
<p>The <a href="https://positron.posit.co/data-explorer" target="_blank" rel="noopener">Data Explorer</a> can now open Excel workbooks directly, with no code required, without first needing to load it via Python or R. Sort, filter, and profile columns, switch between worksheets, and toggle whether the first row holds column names. An <strong>Open in Excel</strong> button opens the workbook in your native spreadsheet application.</p>
<p>This release broadens what you can open in the Data Explorer overall. Backed by a native DuckDB engine, it now also previews compressed CSV, TSV, and Parquet files. Also, a new <strong>Open in Data Explorer</strong> code action, from the editor lightbulb or <kbd>Cmd+.</kbd>, opens the data frame under your cursor in R, Python, and Quarto files, so you can jump straight from your code to exploring your data.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/open-in-data-explorer-code-action.gif" data-fig-align="center" data-fig-alt="The Open in Data Explorer code action in Positron, showing a lightbulb menu in a Python file with the option to open a data frame in the Data Explorer." />
<h2 id="r-language-intelligence">R language intelligence
</h2>
<p>Last release we introduced within-file symbol resolution for R; this release expands it across files. Go to Definition, Find References, and Rename Symbol now work across the packages and scripts in your workspace, and diagnostics and workspace symbols react to external file changes so your language intelligence stays in sync as your project evolves.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>New in preview this release, Data Connections lets you browse the schemas, tables, views, and indexes of a database, open tables in the Data Explorer, and generate connection code. It currently supports DuckDB, PostgreSQL, and SQLite. Learn how to try it out and tell us what you think in the <a href="https://github.com/posit-dev/positron/discussions/14571" target="_blank" rel="noopener">Data Connections discussion post</a>: which databases and warehouses you need, whether the connection setup is clear, and anything confusing, missing, or broken.</li>
<li>We are looking forward to posit::conf(2026) in September, where our team will have several sessions on Positron. <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/featured.svg" length="88493" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>Positron &#43; uv: Python setup in one click</title>
      <link>https://opensource.posit.co/blog/2026-07-08_positron-uv/</link>
      <pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-08_positron-uv/</guid>
      <dc:creator>Isabel Zimmerman</dc:creator><description><![CDATA[<p>The Python community has a long-standing joke about how hard it can be to get Python running on your machine.
The options can feel overwhelming, and the post-install result often looks like this:</p>
<img src="https://imgs.xkcd.com/comics/python_environment.png" data-fig-align="center" data-fig-alt="The xkcd 'Python Environment' comic showing a tangled web of arrows between dozens of conflicting Python installations on one machine" />
<p>If you&rsquo;re new to Python, Positron has a few built-in workflows to help you skip that installation pain entirely.
Most of it is powered by <a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener">uv, created by Astral</a>.
We chose uv because it&rsquo;s fast and has quickly become widely adopted across the Python community.
Behind the scenes, uv handles the heavy lifting, downloading Python versions and building environments.
Positron wraps that power in a friendly interface, so you never have to remember a command.</p>
<h2 id="from-zero-to-python-in-one-click">From zero to Python in one click
</h2>
<p>If you don&rsquo;t have a suitable Python available, Positron will offer to install it for you.
When you go to start a Python runtime through the <strong>Start Session</strong> button, you&rsquo;ll see an option for <strong>+ Install Python via uv</strong>.</p>
<img src="https://opensource.posit.co/blog/2026-07-08_positron-uv/select-interpreter.png" data-fig-align="center" data-fig-alt="Positron's 'Start New Console Session' picker listing installed Python and R interpreters, with a highlighted '+ Install Python via uv' option at the bottom" />
<p>When you choose it, Positron will ask to install uv for you if you don&rsquo;t already have it.
Then it shows you the supported Python versions (currently 3.9 through 3.14) and installs whichever one you pick.
If you have a folder open, it will also offer to create a virtual environment for the project and start a Python console session using that environment.
Once you have a Python available, this option disappears from the session picker.</p>
<p>If you&rsquo;d rather not see the prompt to install Python, you can turn it off with the <a href="positron://settings/python.allowUvPythonInstall"><code>python.allowUvPythonInstall</code></a> setting (enabled by default).</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Why do I see this when I already have Python on my machine?</span>
</div>
<div class="callout-body">
<p>If you only have system Pythons available, you&rsquo;ll still see the <strong>+ Install Python via uv</strong> option.
<a href="https://pydevtools.com/handbook/explanation/why-should-i-avoid-system-python/" target="_blank" rel="noopener">Using system Python tends to cause problems down the road</a>, so Positron nudges you toward a managed Python and virtual environment instead.</p>
</div>
</div>
<h2 id="on-demand-python-installation">On-demand Python installation
</h2>
<p>You don&rsquo;t have to wait for Positron to ask, either.
Open the command palette with <kbd>Cmd/Ctrl+Shift+P</kbd> and run <em>Python: Install Python via uv</em> any time you&rsquo;d like another version.
This command will download uv if needed, show you the available Python versions, and optionally offer to create a virtual environment.</p>
<img src="https://opensource.posit.co/blog/2026-07-08_positron-uv/cmd-uv.png" data-fig-align="center" data-fig-alt="Positron's command palette with 'python install via' typed, showing the 'Python: Install Python via uv' command" />
<p>Since it runs on demand, it&rsquo;s a good way to add another interpreter to a project you&rsquo;ve already set up.
And because it follows the same flow every time, it works well for teaching, since everyone ends up in the same place, the same way.</p>
<h2 id="automatic-project-setup-support">Automatic project setup support
</h2>
<p>Setting up a virtual environment isn&rsquo;t limited to that first run.
If Positron finds a <code>pyproject.toml</code> or <code>requirements.txt</code> in a project that doesn&rsquo;t have a virtual environment yet, it&rsquo;ll offer to create one with uv and install your dependencies.
This comes in handy when you&rsquo;ve cloned a colleague&rsquo;s project and want to get up and running without extra setup.</p>
<img src="https://opensource.posit.co/blog/2026-07-08_positron-uv/requirements.png" data-fig-align="center" data-fig-alt="A Positron notification reading 'This project has requirements.txt but no virtual environment. Use uv to create a .venv and install these requirements?' with Yes, Not now, and Don't show again buttons" />
<p>If there&rsquo;s a single requirements source, like a lone <code>requirements.txt</code> or <code>pyproject.toml</code>, Positron will prompt you to create a <code>.venv</code> and install everything right away.
And if there are several sources, you can choose which files to include in your installation.</p>
<p>Positron also knows when to stay out of your way.
If you already have a local virtual environment, or if you use non-uv files like <code>environment.yml</code>, <code>Pipfile</code>, or <code>poetry.lock</code>, you won&rsquo;t get a prompt at all.</p>
<h2 id="try-it-yourself">Try it yourself
</h2>
<p>Setting up Python doesn&rsquo;t have to feel like a chore, whether you&rsquo;re starting on a brand new machine, picking up a colleague&rsquo;s project, or getting a room full of students ready to code.</p>
<p>Give it a try in <a href="https://positron.posit.co/download" target="_blank" rel="noopener">Positron&rsquo;s July 2026 release</a> and let us know what you think.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-08_positron-uv/image.png" length="386340" type="image/png" />
    </item>
    <item>
      <title>posit::glimpse() Newsletter – July 2026</title>
      <link>https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/</link>
      <pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<blockquote>
<p>Welcome to our newsletter, posit::glimpse()!</p>
<p>If you&rsquo;re currently reading this on our blog, consider subscribing to Product Updates - Open Source on our <a href="https://posit.co/about/subscription-management" target="_blank" rel="noopener">subscription page</a> to receive this newsletter directly in your inbox.</p>
</blockquote>
<p>Welcome to the latest edition of the posit::glimpse() newsletter, the monthly roundup of open-source news for the Posit community. I have many updates for you from across Posit. As my wonderful colleague Kristin Bott stated, “<em>dang</em> this is a productive bunch of humans”.</p>
<p>The table of contents on the right can help you navigate through all the updates. As you scroll, it will open up to show you subcategories →</p>
<h2 id="announcements">Announcements
</h2>
<h3 id="positconf2026-is-happening-soon">posit::conf(2026) is happening soon!
</h3>
<p>Our annual conference, <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">posit::conf(2026)</a>, is happening September 14-16, and we would love to see you there, whether in Houston or online! Check out the <a href="https://posit.co/blog/posit-conf-2026-agenda-breakdown" target="_blank" rel="noopener">speaker lineup</a>, <a href="https://posit.co/blog/workshops-at-positconf2026" target="_blank" rel="noopener">workshop offerings</a>, and <a href="https://conf.posit.co/2026/registration/" target="_blank" rel="noopener">register for posit::conf here</a>.</p>
<p>Tidy Dev Day is happening on September 17, a unique opportunity to collaboratively tackle open-source issues and work directly alongside the very developers who build and maintain the tools you use every day. As <a href="https://thetidytrekker.com/post/vibe-conf-ing/posit_conf_2025" target="_blank" rel="noopener">Meghan Harris stated about last year’s event</a>, “Tidy Dev Day (TDD) gave me the PERFECT opportunity to explore this further in a low-stress, supportive environment.” <a href="https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/" target="_blank" rel="noopener">Learn more about Tidy Dev Day here</a>.</p>
<h3 id="weve-joined-the-jupyter-foundation">We’ve joined the Jupyter Foundation
</h3>
<p>We are proud to announce that we are deepening our commitment to the <a href="https://jupyter.org/" target="_blank" rel="noopener">Jupyter</a> ecosystem by becoming an official <a href="https://jupyterfoundation.org/" target="_blank" rel="noopener">Jupyter Foundation</a> Member!</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/" target="_blank" rel="noopener">We’ve Joined the Jupyter Foundation announcement</a> blog post.</li>
</ul>
<h3 id="announcing-an-expanded-posit-academy">Announcing an expanded Posit Academy
</h3>
<p>We&rsquo;ve launched a new part of <a href="http://academy.posit.co/" target="_blank" rel="noopener">Posit Academy</a>: a free, open library of product courses, hands-on labs, and live workshops available to anyone.</p>
<ul>
<li>Learn more in the <a href="https://posit.co/blog/announcing-expanded-posit-academy" target="_blank" rel="noopener">Announcing an expanded Posit Academy</a> blog post.</li>
</ul>
<h3 id="introducing-the-posit-impact-awards">Introducing the Posit Impact Awards
</h3>
<p>Have a story to share? We just launched the Posit Impact Awards to recognize individuals and teams who used Posit to create measurable, meaningful change. Six winners will be selected, one per category, and each will receive a conference-only pass to posit::conf(2026).</p>
<ul>
<li><a href="https://docs.google.com/forms/d/e/1FAIpQLSfQrWnEQ_wlc5lhyn5BLgU0mvfWDXb1XSXhq9PoSERdWSZS3g/viewform" target="_blank" rel="noopener">Submit your nomination before July 20th.</a></li>
</ul>
<h2 id="key-product-updates-and-new-releases">Key product updates and new releases
</h2>
<h3 id="data-visualization-and-reporting">Data visualization and reporting
</h3>
<h4 id="ggsql-041">ggsql 0.4.1
</h4>
<p><a href="https://ggsql.org/" target="_blank" rel="noopener">ggsql</a> 0.4.1 introduces spatial plotting capabilities with database-backed geometry processing, supporting WKB format data, 21 map projections for cartographic accuracy, and a built-in Natural Earth world dataset for creating choropleth maps and geographic visualizations with backends like DuckDB spatial, PostGIS, and SpatiaLite.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-23_ggsql_0_4_1/" target="_blank" rel="noopener">ggsql 0.4.1: Spatial plotting and in-layer aggregation</a> blog post.</li>
</ul>
<h4 id="ask-more-of-your-dashboard-with-querychat-and-ggsql">Ask more of your dashboard with querychat and ggsql
</h4>
<p><a href="https://posit-dev.github.io/querychat/" target="_blank" rel="noopener">querychat</a> now supports ggsql-powered visualizations, enabling natural language data exploration in dashboards through SQL-only execution (no arbitrary code), with three pre-built tools for visualizing, querying, and filtering data reactively. The package works in both Python and R, integrates with <a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a> dashboards, and supports Snowflake Semantic Models for business logic definitions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/" target="_blank" rel="noopener">Ask more of your dashboard with querychat and ggsql</a> blog post.</li>
</ul>
<h4 id="great-tables-0220">Great Tables 0.22.0
</h4>
<p><a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">Great Tables</a> v0.22.0 significantly expands Python table presentation capabilities with footnote support, group-wise and grand summary row calculations, column merging utilities for uncertainty and ranges, text transformation methods, value substitution helpers, duration and parts-per formatters, image export functionality via gtsave(), enhanced LaTeX rendering, and makes Pandas an optional dependency for Polars-only workflows. (impressive update!)</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/" target="_blank" rel="noopener">Great Tables v0.22.0</a> blog post.</li>
</ul>
<h3 id="data-access">Data access
</h3>
<h4 id="dbplyr-260">dbplyr 2.6.0
</h4>
<p><a href="https://dbplyr.tidyverse.org/" target="_blank" rel="noopener">dbplyr</a> 2.6.0 introduces ADBC support via adbi for faster Arrow-based data transfer, JDBC support, new SQL dialect separation, and query composition functions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/" target="_blank" rel="noopener">dbplyr 2.6.0</a> blog post.</li>
</ul>
<h4 id="webr-060">webR 0.6.0
</h4>
<p><a href="https://docs.r-wasm.org/webr/latest/" target="_blank" rel="noopener">webR</a> 0.6.0 upgrades to R 4.6.0 and adds async/await support for JavaScript Promises, curl and httr2 compatibility through WebSocket traffic proxying, modern Fortran fixes for expanded package support, and updated system libraries including OpenSSL 3.5.1 and Emscripten 5.0.7. The release powers interactive R experiences in Quarto Live and Shinylive.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/" target="_blank" rel="noopener">webR 0.6.0</a> blog post.</li>
</ul>
<h3 id="developer-tools-and-ai">Developer tools and AI
</h3>
<h4 id="debrief-010">debrief 0.1.0
</h4>
<p>The <a href="https://r-lib.github.io/debrief/" target="_blank" rel="noopener">debrief</a> package converts profvis profiling output into text-based summaries designed for AI agents, enabling AI-assisted performance optimization by providing structured reports on hotspots, call trees, and memory allocations that AI systems can read and act upon.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/" target="_blank" rel="noopener">debrief 0.1.0</a> blog post.</li>
</ul>
<h4 id="pkgsite-010">pkgsite 0.1.0
</h4>
<p><a href="https://edgararuiz.github.io/pkgsite/" target="_blank" rel="noopener">pkgsite</a> 0.1.0 converts R package .Rd documentation files into <a href="https://quarto.org/" target="_blank" rel="noopener">Quarto</a> .qmd files, enabling custom documentation sites with Quarto’s freeze feature for local example rendering, unified R/Python documentation when combined with Quartodoc, and flexible template customization. The package is available on CRAN and provides an alternative to pkgdown.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/" target="_blank" rel="noopener">pkgsite 0.1.0</a> blog post.</li>
</ul>
<h4 id="watcher-020">watcher 0.2.0
</h4>
<p><a href="https://watcher.r-lib.org/" target="_blank" rel="noopener">Watcher</a> is a lightweight R package that watches files and directories for changes and reacts in the background. It’s quietly been the engine behind Shiny&rsquo;s auto-reload for the past year. With the CRAN release of 0.2.0, we&rsquo;re excited to introduce it as a general-purpose filesystem watcher for R developers.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/" target="_blank" rel="noopener">watcher 0.2.0</a> blog post.</li>
</ul>
<h3 id="development-environment">Development environment
</h3>
<h4 id="air-0100">Air 0.10.0
</h4>
<p><a href="https://posit-dev.github.io/air/" target="_blank" rel="noopener">Air</a> 0.10.0 introduces configurable assignment style enforcement allowing teams to standardize on arrow (<code>&lt;-</code>), equal (<code>=</code>), or preserve existing styles, along with enhanced IDE integrations for Positron and RStudio, multiple installation methods via PyPI and conda-forge, pre-commit hook support, stdin integration for editors, and shell completions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/" target="_blank" rel="noopener">Air 0.10.0</a> blog post.</li>
</ul>
<h4 id="whats-new-in-positron">What’s new in Positron
</h4>
<p><a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>’s June release includes a lot of highly requested features:</p>
<ul>
<li>Inline output for Quarto (one of Positron’s most-requested features ever!)</li>
<li>Posit Assistant, the successor to Positron Assistant</li>
<li>Packages pane improvements</li>
<li>A more customizable interface</li>
</ul>
<p>For more, check out the <a href="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/" target="_blank" rel="noopener">Positron June Release Highlights</a> post and <a href="https://posit.co/positron-updates-signup" target="_blank" rel="noopener">subscribe to Positron emails</a>.</p>
<p>Did you know that many of the most upvoted RStudio feature requests are already implemented in Positron? Learn about ten of them in the <a href="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/" target="_blank" rel="noopener">RStudio’s Top Feature Requests … In Positron blog post</a>!</p>
<h3 id="machine-learning-and-modeling">Machine learning and modeling
</h3>
<h4 id="brulee-100">brulee 1.0.0
</h4>
<p><a href="https://brulee.tidymodels.org/" target="_blank" rel="noopener">brulee</a> 1.0.0 significantly expands tabular deep learning capabilities in R with five new model architectures, GPU support including Apple Silicon, 32-bit precision for improved performance, and enhanced numerical stability, all integrated with the tidymodels ecosystem.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/" target="_blank" rel="noopener">brulee 1.0.0</a> blog post.</li>
</ul>
<h4 id="catboost-support-in-tidymodels">CatBoost support in tidymodels
</h4>
<p>CatBoost gradient boosting support is now available in <a href="https://www.tidymodels.org/" target="_blank" rel="noopener">tidymodels</a> through the <code>boost_tree()</code> interface, providing access to CatBoost’s strong categorical feature handling with full tidymodels integration including hyperparameter tuning, cross-validation, efficient submodel optimization, and orbital package support for SQL generation and in-database predictions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/" target="_blank" rel="noopener">CatBoost support in tidymodels</a> blog post.</li>
</ul>
<h4 id="tidyclust-030">tidyclust 0.3.0
</h4>
<p><a href="https://tidyclust.tidymodels.org/index.html" target="_blank" rel="noopener">tidyclust</a> 0.3.0 introduces three new clustering model families and achieves full integration with tidymodels by replacing tidyclust-specific functions with native tune package support.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/" target="_blank" rel="noopener">tidyclust 0.3.0</a> blog post.</li>
</ul>
<h2 id="event-roundup">Event roundup
</h2>
<p>We were all over the world this month, discussing how to adopt new data tools, get better at old ones, and just loving being part of the community. If you want to learn more about where we were, or see where we’ll be next, check out our <a href="https://opensource.posit.co/events/" target="_blank" rel="noopener">event page</a>.</p>
<p>Watch the recordings from some of these events:</p>















  
  
  
  
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  



<div class="grid grid-cols-2 gap-6 xl:gap-8 xl:gap-8 mb-6">
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  



  

  

  

  

  

  

  

  

  

  

  

  

  

  




















  
    
  





  
    
  
























<div id="item-27ed749654bd2d6c044eb1d6fb3bf2e9" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/" title="Neal Richardson - MCP, or not MCP | Pydata London 26"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_2e4f2a3a58db4627.webp 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_3bea74d726538bdd.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_7524b87c91de63ad.jpg"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_7524b87c91de63ad.jpg 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_e7f65162a2cb51e6.jpg 2x"
          alt="Neal Richardson - MCP, or not MCP | Pydata London 26"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_3bea74d726538bdd.webp 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_168415f79709c6d1.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_e7f65162a2cb51e6.jpg"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_e7f65162a2cb51e6.jpg 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_409671ed69280e4b.jpg 2x"
          alt="Neal Richardson - MCP, or not MCP | Pydata London 26"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_168415f79709c6d1.webp 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_3829c4c9d7cfb126.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_409671ed69280e4b.jpg"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_409671ed69280e4b.jpg 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_99b33ee74fa66f52.jpg 2x"
          alt="Neal Richardson - MCP, or not MCP | Pydata London 26"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 15, 2026</span>
          <span>|</span>
        
        
        
          <span>34 min</span>
          <span>|</span>
        
        
          <span>308 views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Neal Richardson - MCP, or not MCP | Pydata London 26</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">Neal Richardson - MCP, or not MCP | Pydata London 26 \Model Context Protocol is a standard for defining tools that can be made available to LLMs and AI applications. There’s a lot of noise out there about what you should use to get the best results from AI, so in this talk, I will provide some guidance on when you should use MCP, and when you should reach for some other tool. I will describe cases where MCP is the right tool for the job, and when other things, like skills or other context files, are better. I will also devote attention to questions of security and authentication, which are important for MCP, and provide concrete examples of how MCP servers can be used to unlock agentic workflows while also strengthening data governance. This talk is intended for those who are interested in using LLMs for workflows involving data. No prior experience with MCP is required.
Outline:
Intro: how can I get data from this API into my Claude Code session? What is MCP? When should you use it, when should you use other tools Work through an example Sharing and deploying MCP servers, alternatives and best practices Optimizing your tools for best results www.pydata.org
PyData is an educational program of NumFOCUS, a 501(c)3 non-profit organization in the United States. PyData provides a forum for the international community of users and developers of data analysis tools to share ideas and learn from each other. The global PyData network promotes discussion of best practices, new approaches, and emerging technologies for data management, processing, analytics, and visualization. PyData communities approach data science using many languages, including (but not limited to) Python, Julia, and R.
PyData conferences aim to be accessible and community-driven, with novice to advanced level presentations. PyData tutorials and talks bring attendees the latest project features along with cutting-edge use cases.
00:00 Welcome! 00:10 Help us add time stamps or captions to this video! See the description for details.
Want to help add timestamps to our YouTube videos to help with discoverability? Find out more here: https://github.com/numfocus/YouTubeVideoTimestamps
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/neal-richardson/profile.jpg" 
          alt="Neal Richardson" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Neal Richardson</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                Python
              </span>
            
              <span class="pill">
                Tutorial
              </span>
            
              <span class="pill">
                Education
              </span>
            
              <span class="pill">
                NumFOCUS
              </span>
            
              <span class="pill">
                PyData
              </span>
            
              <span class="pill">
                Opensource
              </span>
            
              <span class="pill">
                Learn
              </span>
            
              <span class="pill">
                Software
              </span>
            
              <span class="pill">
                Python 3
              </span>
            
              <span class="pill">
                Julia
              </span>
            
              <span class="pill">
                Coding
              </span>
            
              <span class="pill">
                Learn to Code
              </span>
            
              <span class="pill">
                How to Program
              </span>
            
              <span class="pill">
                Scientific Programming
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-0bea2d9d122698cc4c259ae2a49344c9" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/" title="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_b03c3320e4321048.webp 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_709131f576b34e9b.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_946b93830a7ce124.jpg"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_946b93830a7ce124.jpg 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_6f49f3aa2116d34a.jpg 2x"
          alt="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_709131f576b34e9b.webp 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_7b164a87556be464.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_6f49f3aa2116d34a.jpg"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_6f49f3aa2116d34a.jpg 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_2dcdb2aac4711fb1.jpg 2x"
          alt="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_7b164a87556be464.webp 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_a0b0c1c85b31ff70.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_2dcdb2aac4711fb1.jpg"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_2dcdb2aac4711fb1.jpg 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_f7c8a43de74d743a.jpg 2x"
          alt="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 26, 2026</span>
          <span>|</span>
        
        
        
          <span>47 min</span>
          <span>|</span>
        
        
          <span>179 views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman (Posit)
Abstract: How do we build competent data analysis agents? Data analysis requires a willingness to pause, question conclusions, and dig into subtleties. Frontier LLMs, however, are optimized to push tasks toward completion, not to slow down when something seems off. This tendency works well for coding agents, where success is often verifiable. But for data analysis, verification is more complicated, and autonomous work by the agent can be at odds with the spirit of the discipline. Drawing on our experience building data analysis agents, we&rsquo;ll share evaluations that expose where LLM-driven analysis goes wrong and design patterns that keep analyses correct, transparent, and reproducible.
Resources mentioned in the session:
Presentation Slides: https://simonpcouch.github.io/gen-ai-pharma-26 Presentation GitHub repository: https://github.com/simonpcouch/gen-ai-pharma-26 bluffbench: https://github.com/simonpcouch/bluffbench Posit Assistant Terminal (TUI): https://posit-dev.github.io/assistant/docs/downloads/tui/ Posit AI Newsletter: https://opensource.posit.co/tags/ai-newsletter/ Speakers:
Simon Couch builds tools that make the work of data science more joyful and effective. As an engineer on the AI Core Team at Posit, his work spans coding agents, model evaluations, inference engineering, and next-edit-suggestion systems. Drawing on his background in statistics, Simon spent several years authoring and maintaining core packages in the open-source tidymodels framework—like stacks, broom, and infer — before shifting his focus to LLMs. He blogs about his work at simonpcouch.com. Simon authors the Posit AI Newsletter along with Sara Altman.
Sara Altman is a Senior Developer Advocate on the AI Core team at Posit, where she focuses on how AI can be effectively and responsibly used for data science. Previously, she helped build Posit Academy and taught data science and R at Stanford. Sara authors the Posit AI Newsletter along with Simon Couch.
Presented at the 2026 R/Pharma GenAI Day
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/sara-altman/profile.jpg" 
          alt="Sara Altman" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ><img 
          src="https://opensource.posit.co/people/simon-couch/profile.jpg" 
          alt="Simon Couch" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white -ml-2"
          style="z-index: 9;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Sara Altman,&nbsp;Simon Couch</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                infer
              </span>
            
              <span class="pill">
                tidymodels
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
</div>


<h2 id="showcases-from-the-community">Showcases from the community
</h2>









  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><p>Brilliant Earth turned their Marketing Mix Model into a <a href="https://streamlit.io/" target="_blank" rel="noopener">Streamlit</a> app deployed on <a href="https://posit.co/products/enterprise/connect" target="_blank" rel="noopener">Posit Connect</a> via the <a href="https://docs.posit.co/partnerships/snowflake/" target="_blank" rel="noopener">Snowflake Native App</a>, so their marketing team can dig into channel performance and run scenario planning on their own. One of the campaigns in the mix: their recent Ring Pop collaboration.</p>
<ul>
<li><a href="https://posit.co/about/customer-stories/brilliant-earth" target="_blank" rel="noopener">Check out the spotlight.</a></li>
</ul></div>
    
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/images/image01.png"
        alt="Brilliant Earth logo with rings in the background." 
        loading="lazy"
      >
    </figure></div></div>
    
  
</div>










  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/images/image02.png"
        alt="Dashboard showing Lorcana market data analysis with multiple charts displaying price trends and forecasting visualizations" 
        loading="lazy"
      >
    </figure></div></div>
    
  
    
    
    
      <div class="prose max-w-none "><p><a href="https://www.linkedin.com/in/leo-ohyama-phd-52358387/" target="_blank" rel="noopener">Leo Ohyama</a> recently shared a fantastic Lorcana Market Data Analysis &amp; Forecasting dashboard that showcases the power of Python, R, Positron, and Quarto working together.</p>
<p>Leo included detailed documentation in the GitHub repository, a great resource for anyone interested in market forecasting or multi-tool workflows. Thanks for sharing your work with the community, Leo!</p>
<ul>
<li><a href="https://lorecaster.ink/" target="_blank" rel="noopener">Lorcana dashboard</a></li>
<li><a href="https://github.com/leoohyama/lorcana" target="_blank" rel="noopener">Lorcana GitHub repo</a></li>
</ul>
</div>
    
  
</div>










  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><p>We were recently joined by <a href="https://opensource.posit.co/people/thomas-lin-pedersen/" target="_blank" rel="noopener">Thomas Lin Pedersen</a> on the <a href="https://pos.it/dslab" target="_blank" rel="noopener">Data Science Lab</a>, where he introduced the new <a href="https://ggsql.org/" target="_blank" rel="noopener">ggsql</a> package.</p>
<p>(Almost) immediately after the DS Lab, <a href="https://www.linkedin.com/in/drspoulsen/" target="_blank" rel="noopener">Dylan Poulsen</a> wrote a blog post on exploring two years of swim data with ggsql! Dylan, we’re convinced you write blogs at the speed of ggsql.</p>
<ul>
<li><a href="https://dylanpoulsen.com/posts/2026-06-16-ggsql-swimming.html" target="_blank" rel="noopener">Exploring the New ggsql Package with Two Years of Swim Data</a> blog post</li>
</ul>
</div>
    
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/images/image03.png"
        alt="Visualization created with ggsql showing swim data analysis with line charts tracking performance over time" 
        loading="lazy"
      >
    </figure></div></div>
    
  
</div>

<p>We usually find these projects on social media. If you’re on LinkedIn, be sure to follow and tag <a href="https://www.linkedin.com/showcase/posit-open-source/" target="_blank" rel="noopener">Posit Open Source</a> for us to share the amazing things you’re working on!</p>
<h2 id="whats-next">What’s next
</h2>
<p>We’re taking a short break in July before returning with more community hangouts!</p>
<ul>
<li>On July 21, Gina Reynolds will join the Data Science Lab (a fun, chill time with live code) to show we can extend ggplot2 by creating our own custom extensions. Register here: <a href="https://pos.it/dslab" target="_blank" rel="noopener">https://pos.it/dslab</a></li>
</ul>
<p>In the meantime, check out some past Data Science Lab episodes:</p>















  
  
  
  
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  



<div class="grid grid-cols-3 gap-6 xl:gap-8 xl:gap-8 mb-6">
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-e1a7fb00aebe5239e2fb98a5bfb01eea" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/" title="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_c3d165a6da89ccf6.webp 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_e010d66820a12428.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_39e3051bf743594c.jpg"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_39e3051bf743594c.jpg 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_8760612808811c9a.jpg 2x"
          alt="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_e010d66820a12428.webp 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_1e8d89ec085c5bba.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_8760612808811c9a.jpg"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_8760612808811c9a.jpg 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_ed5d4972ccad3c7.jpg 2x"
          alt="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_1e8d89ec085c5bba.webp 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_897267d62d43465d.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_ed5d4972ccad3c7.jpg"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_ed5d4972ccad3c7.jpg 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_6f4f6696051268a6.jpg 2x"
          alt="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>May 29, 2026</span>
          <span>|</span>
        
        
        
          <span>57 min</span>
          <span>|</span>
        
        
          <span>1.3k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Charlie Gao, who walks through async and parallel programming using the mirai package for R. Charlie is the author of {mirai} and {mori}!
Charlie demonstrates how mirai lets you run R code in parallel across multiple cores or even distributed across remote machines. He covers the key differences between parallel and async programming, shows how to set up worker daemons (said like &ldquo;demons&rdquo;), scale resources dynamically, and connect to remote machines via SSH. Whether you&rsquo;re running long computations, training models, or building Shiny apps, mirai helps you make the most of your computing resources without blocking your main R session:)
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Charlie Gao&rsquo;s GitHub: https://github.com/shikokuchuo Charlie Gao&rsquo;s Bluesky: https://bsky.app/profile/shikokuchuo.net Charlie Gao&rsquo;s LinkedIn: https://www.linkedin.com/in/charliegao/ Charlie Gao&rsquo;s Mastodon: https://fosstodon.org/@shikokuchuo
Resources mentioned in the video and chat: mirai package website: https://mirai.r-lib.org/ mirai GitHub repository: https://github.com/r-lib/mirai AskDeepSeek chatbot for mirai documentation: https://mirai.r-lib.org/ (click &ldquo;ask deep wiki&rdquo; button) mirai - Promises (Shiny and Plumber): https://mirai.r-lib.org/articles/v02-promises.html mirai - Serialization: https://mirai.r-lib.org/articles/v03-serialization.html mirai - OpenTelemetry: https://mirai.r-lib.org/articles/v05-opentelemetry.html mirai stop_mirai function: https://mirai.r-lib.org/reference/stop_mirai.html mirai skill for Claude Code: https://github.com/r-lib/mirai/blob/main/.claude/skills/mirai/SKILL.md Plumber2 package: https://plumber2.posit.co/ Daemon (computing) on Wikipedia: https://en.wikipedia.org/wiki/Daemon_(computing)
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu Follow Us Here: Website: https://www.posit.co Hangout: https://pos.it/dsh The Lab: https://pos.it/dslab LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for hanging out with us!
Timestamps of Questions / Topics: 00:00 Introduction 03:33 &ldquo;What is async programming and how is it different from parallel?&rdquo; 06:57 AskDeepSeek chatbot for the Mirai package 09:40 Setting up the Positron IDE with activity bar on top 15:25 &ldquo;What was the motivation or need for developing Mirai?&rdquo; 22:10 Mirai map function for parallel processing 25:25 &ldquo;Do the contents of Mirai inherit definitions from the global environment?&rdquo; 29:00 &ldquo;What&rsquo;s the difference between Mirai versus promises and future?&rdquo; 31:50 Demonstrating sequential vs parallel processing 36:00 &ldquo;Can you use Mirai with HPC?&rdquo; 37:07 Dynamically scaling workers by adding and removing daemons 38:38 Setting up daemons with URLs for network connections 42:05 Launching workers over SSH to remote machines 43:09 SSH tunneling to connect workers without open ports 51:14 &ldquo;Does Mirai allow R to perform the same thing as NumPy?&rdquo; 51:54 &ldquo;Does Mirai ship with some kind of task viewing dashboard?&rdquo; 52:35 &ldquo;Can we use parallel::detectCores() to see how many daemons we can use?&rdquo; 53:58 &ldquo;Would parallel processing be more useful than async processing in typical data science work?&rdquo; 55:00 Mirai skill for Claude Code and AI agents
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/charlie-gao/profile.jpg" 
          alt="Charlie Gao" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Charlie Gao</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                mirai
              </span>
            
              <span class="pill">
                mori
              </span>
            
              <span class="pill">
                plumber
              </span>
            
              <span class="pill">
                plumber2
              </span>
            
              <span class="pill">
                Positron
              </span>
            
              <span class="pill">
                Shiny
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-451ed6564c115dcc7bdce93d95ee2802" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/" title="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_a23a02bc126732e3.webp 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_36f7dc1a902e03a.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_7d73634aa10248d.jpg"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_7d73634aa10248d.jpg 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_53383455a06a605f.jpg 2x"
          alt="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_36f7dc1a902e03a.webp 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_5a96a040aff01af5.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_53383455a06a605f.jpg"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_53383455a06a605f.jpg 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d35a1a3e9dce2346.jpg 2x"
          alt="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_5a96a040aff01af5.webp 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_264773719908e6d9.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d35a1a3e9dce2346.jpg"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d35a1a3e9dce2346.jpg 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d52ea0ae3df67b45.jpg 2x"
          alt="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 23, 2026</span>
          <span>|</span>
        
        
        
          <span>58 min</span>
          <span>|</span>
        
        
          <span>4.9k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Hadley Wickham, who walks through using data dictionaries with Claude Code to clean and document datasets effectively.
Hadley demonstrates a workflow using three files: a data cleaning script, a data dictionary in YAML format, and the final cleaned data as a Parquet file. He shows how Claude Code and MCP REPL can help generate and maintain data dictionaries that document what you know about your data, making it easier for both humans and AI agents to work with your datasets. Using the NYC elevators dataset as an example, he walks through data cleaning tasks like normalizing whitespace, handling missing values, fixing date formats, and investigating geocoding issues - all while keeping the data dictionary, cleaning script, and Parquet file in sync through git.
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Hadley Wickham&rsquo;s GitHub: https://github.com/hadley Hadley Wickham&rsquo;s Bluesky: https://bsky.app/profile/hadley.nz Hadley Wickham&rsquo;s LinkedIn: https://www.linkedin.com/in/hadleywickham/
Resources mentioned in the video and chat: MCP REPL: https://github.com/posit-dev/mcp-repl Data Dictionary YAML Format Specification: https://github.com/hadley/data-dict.yaml Parquet files in R for Data Science: https://r4ds.hadley.nz/arrow.html#sec-parquet Elevators Dataset Used in Demo: https://github.com/EmilHvitfeldt/elevators Pointblank Package for Data Validation: https://posit-dev.github.io/pointblank/ Arrow R Book: https://arrowrbook.com/ Monaspace Font Family (with ligatures): https://monaspace.githubnext.com/ YAML Multiline Strings Reference: https://yaml-multiline.info/ UBC Course on Shiny with RAG and Parquet: https://ubc-mds.github.io/DSCI_532_vis-2_book/060-03-rag.html Tom Scott Video on Timezones: https://www.youtube.com/watch?v=-5wpm-gesOY Falsehoods Programmers Believe About Names: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/ UTC Is Enough for Everyone Right: https://zachholman.com/talk/utc-is-enough-for-everyone-right Daniel Chen&rsquo;s Cherry Blossom Analysis: https://chendaniely.github.io/posts/2026/2026-03-30-yvr-cherry-blossoms-marathon/ Project Drawdown Climate Impact Explorer: https://drawdown.org/explorer Green Coding Bookdown Resource: https://bookdown.org/content/d1e53ac9-28ce-472f-bc2c-f499f18264a3/ IBM Green Coding Topics: https://www.ibm.com/think/topics/green-coding Secret Elevator in Central Park Article: https://undercovernyc.home.blog/2021/02/08/a-secret-elevator-hidden-in-central-park/ Artificial Cave Beneath Central Park: https://gizmodo.com/an-artificial-cave-200-beneath-central-park-with-micha-1446538828
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu Follow Us Here: Website: https://www.posit.co Hangout: https://pos.it/dsh The Lab: https://pos.it/dslab LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for hanging out with us!
Timestamps of Questions / Topics: 00:00 Introduction 03:38 &ldquo;Can you talk a little bit about what MCP is?&rdquo; 05:42 Introducing the elevators dataset 07:00 Creating initial data dictionary with Claude 09:03 &ldquo;Are there any cases where the CSV format is actually a better choice than Parquet?&rdquo; 09:50 &ldquo;What font do you use?&rdquo; 12:02 Adding context from the readme to the data dictionary 14:02 &ldquo;What is a good way to store data dictionaries along with datasets?&rdquo; 14:38 &ldquo;Is this particular data dict YAML format useful for projects with only one table of data?&rdquo; 17:45 &ldquo;Is Claude also going to decide when it&rsquo;s a good time to make a commit?&rdquo; 24:02 &ldquo;Does MCP REPL work on a Windows machine and can one use other AI LLM for example ChatGPT with it?&rdquo; 26:14 Converting date columns to proper date types 27:16 &ldquo;Do you see a meaningful distinction between data dicts and data contracts?&rdquo; 29:36 &ldquo;How do you view your approach to data dictionaries and the development of pointblank?&rdquo; 34:16 Eliminating placeholder values and using proper missing values 36:25 &ldquo;Could you show off a diff of a Parquet file?&rdquo; 38:25 Investigating geocodes and creating a map of elevators 40:20 Using a leaflet map to explore Central Park elevators 42:03 &ldquo;Which model is Hadley using?&rdquo; 43:55 Discussion of cost consciousness and environmental impact of LLMs 46:30 &ldquo;Is there a way to quantify environmental and electrical costs?&rdquo; 48:50 The mystery elevator in Central Park 54:23 &ldquo;How do you know this is actually faster or more productive than just writing the code?&rdquo; 55:45 The importance of deep knowledge of data in qualitative work
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/hadley-wickham/profile.jpg" 
          alt="Hadley Wickham" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Hadley Wickham</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                bookdown
              </span>
            
              <span class="pill">
                bookdown.org
              </span>
            
              <span class="pill">
                leaflet
              </span>
            
              <span class="pill">
                pointblank
              </span>
            
              <span class="pill">
                Shiny
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  
    
  






















  
    
  





  
    
  
























<div id="item-4d9af617f6704fb38d6f46af20c8ee48" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/" title="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_b342729d310b0653.webp 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d213a05d6559bee.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_5eb9cd45b958caea.jpg"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_5eb9cd45b958caea.jpg 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d34cd327bb21020.jpg 2x"
          alt="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d213a05d6559bee.webp 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7968fe6f5db9f34d.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d34cd327bb21020.jpg"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d34cd327bb21020.jpg 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7edb6814911cc29d.jpg 2x"
          alt="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7968fe6f5db9f34d.webp 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_74bb543c4243eb29.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7edb6814911cc29d.jpg"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7edb6814911cc29d.jpg 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_b7f23d7c59f3ea7c.jpg 2x"
          alt="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Feb 19, 2026</span>
          <span>|</span>
        
        
        
          <span>55 min</span>
          <span>|</span>
        
        
          <span>1.7k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Using R package structure for data science projects | Kylie Ainslie | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Kylie Ainslie who walks through how structuring data science projects as R packages provides a consistent framework that integrates documentation for you and facilitates collaboration with others by organizing things really well. Kylie says, &ldquo;I stumbled on using an R package structure to organize my projects a number of years ago and it has changed how I work in such a positive way that I want to share it with others! In a world where our attention is constantly being pulled in many directions, efficiency is crucial. Structuring projects as R packages is how I work more efficiently.&rdquo;
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Kylie&rsquo;s Bluesky: @kylieainslie.bsky.social Kylie&rsquo;s LinkedIn: https://www.linkedin.com/in/kylieainslie/ Kylie&rsquo;s Website: https://kylieainslie.github.io/ Kylie&rsquo;s GitHub: https://github.com/kylieainslie
Resources from the hosts and chat:
posit::conf(2026) call for talks: https://posit.co/blog/posit-conf-2026-call-for-talks/ Kylie&rsquo;s posit::conf(2025) talk: https://www.youtube.com/watch?v=YzIiWg4rySA {usethis} package: https://usethis.r-lib.org/ R Packages (2e) book: https://r-pkgs.org/ Paquetes de R (R Packages in Spanish): https://davidrsch.github.io/rpkgs-es/ {box} package: https://github.com/klmr/box extdata docs in Writing R Extensions: https://cran.r-project.org/doc/manuals/R-exts.html#Data-in-packages-1 Tan Ho&rsquo;s talk on NFL data: https://tanho.ca/talks/rsconf2022-github/ {rv} package: https://a2-ai.github.io/rv-docs/ Whether to Import or Depend: https://r-pkgs.org/dependencies-mindset-background.html#sec-dependencies-imports-vs-depends {pkgdown} package: https://pkgdown.r-lib.org/ Edgar Ruiz&rsquo;s {pkgsite} package: https://github.com/edgararuiz/pkgsite
Attendees shared examples of data packages in the chat! Here they are: https://kjhealy.github.io/nycdogs/ https://kjhealy.github.io/gssr/ https://github.com/deepshamenghani/richmondway https://github.com/kyleGrealis/nascaR.data https://github.com/ivelasq/leaidr
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu
Follow Us Here: Website: https://www.posit.co The Lab: https://pos.it/dslab Hangout: https://pos.it/dsh LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for learning with us!
Timestamps: 00:00 Introduction 06:17 Reviewing the disorganized project example 10:01 Creating the package structure using create_package 17:50 Organizing external data and scripts in the inst folder 22:55 Adding a README and License 29:06 &ldquo;What are the advantages to packaging a project?&rdquo; 33:35 Writing Roxygen2 documentation 36:06 &ldquo;Do you type return at the end of your functions?&rdquo; 41:55 Handling dependencies with use_package 43:53 &ldquo;Can you just use require(dplyr) at the top?&rdquo; 47:45 Setting up a pkgdown site 50:11 Creating vignettes 52:22 &ldquo;What is the role of the usethis package?&rdquo; 54:18 Loading the package with devtools::load_all
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/edgar-ruiz/profile.jpg" 
          alt="Edgar Ruiz" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Edgar Ruiz</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                devtools
              </span>
            
              <span class="pill">
                dplyr
              </span>
            
              <span class="pill">
                pkgdown
              </span>
            
              <span class="pill">
                roxygen2
              </span>
            
              <span class="pill">
                usethis
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
</div>


<p>I’m a real person, and I would love to know how to make the Glimpse newsletter better! Find me on <a href="https://www.linkedin.com/in/ivelasq/" target="_blank" rel="noopener">LinkedIn</a> and <a href="https://bsky.app/profile/ivelasq3.bsky.social" target="_blank" rel="noopener">Bluesky</a>, or email me at isabella [dot] velasquez [at] posit.co.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/thumbnail.jpg" length="68574" type="image/jpeg" />
    </item>
    <item>
      <title>raghilda `v0.2`: crawl APIs, PostgreSQL, and more</title>
      <link>https://opensource.posit.co/blog/2026-07-01_raghilda-0-2-0/</link>
      <pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-01_raghilda-0-2-0/</guid>
      <dc:creator>Rich Iannone</dc:creator>
      <dc:creator>Tomasz Kalinowski</dc:creator><description><![CDATA[<p>When we <a href="https://opensource.posit.co/blog/2026-04-14_rag-with-raghilda/" target="_blank" rel="noopener">introduced
raghilda</a>
in April, the package handled the core RAG workflow: read documents,
chunk them, embed them into a store, and retrieve relevant chunks at
query time. raghilda <code>v0.2</code> broadens that scope considerably. The
release adds a structured crawl and ingest API that replaces the manual
read-and-upsert loop with a pipeline built around caching, concurrency,
and composable crawlers (including a <code>CloudflareCrawler</code> that can index
JavaScript-rendered sites without running a local headless browser). It
also ships a PostgreSQL store backend and NVIDIA NIM embedding support.</p>
<p>This post walks through the major additions. The package&rsquo;s fundamentals
have not changed (stores, chunkers, retrievers, and the pattern for
connecting to chatlas all work as before), but the surface area for
building and maintaining stores in production has grown substantially.</p>
<h2 id="the-crawl-and-ingest-api">The crawl and ingest API
</h2>
<p>The largest change in <code>v0.2</code> is a new API for crawling sources and
ingesting them into a store. In <code>v0.1</code>, building a store meant calling
<code>read_as_markdown()</code> on individual URLs, chunking each result, and
upserting them one by one. That works for a handful of pages, but it
becomes unwieldy for larger collections, where you also want caching (to
avoid re-fetching unchanged content) and concurrency (to finish in
minutes rather than hours).</p>
<p>The new API introduces a clean separation between crawling and storage.
On the crawl side, a crawler object produces <code>MarkdownDocument</code> objects
from a defined scope. On the store side, <code>store.ingest()</code> consumes those
documents lazily, applies an optional preparation step (typically
chunking), and writes them to the store with configurable parallelism.
The pipeline looks like this:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.chunker</span> <span class="kn">import</span> <span class="n">MarkdownChunker</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.crawl</span> <span class="kn">import</span> <span class="n">CrawlScope</span><span class="p">,</span> <span class="n">WebCrawler</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.embedding</span> <span class="kn">import</span> <span class="n">EmbeddingOpenAI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.store</span> <span class="kn">import</span> <span class="n">DuckDBStore</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">crawler</span> <span class="o">=</span> <span class="n">WebCrawler</span><span class="p">(</span><span class="n">cache_dir</span><span class="o">=</span><span class="s2">&#34;.cache/crawl&#34;</span><span class="p">,</span> <span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">scope</span> <span class="o">=</span> <span class="n">CrawlScope</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">roots</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;https://example.com/docs&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">include_patterns</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;https://example.com/docs/**&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">depth</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">documents</span> <span class="o">=</span> <span class="n">crawler</span><span class="o">.</span><span class="n">markdown_documents</span><span class="p">(</span><span class="n">scope</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">DuckDBStore</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">location</span><span class="o">=</span><span class="s2">&#34;raghilda.duckdb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">embed</span><span class="o">=</span><span class="n">EmbeddingOpenAI</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">overwrite</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">summary</span> <span class="o">=</span> <span class="n">store</span><span class="o">.</span><span class="n">ingest</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">documents</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">prepare</span><span class="o">=</span><span class="n">MarkdownChunker</span><span class="p">(</span><span class="n">chunk_size</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span><span class="o">.</span><span class="n">chunk</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">summary</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>IngestSummary(inserted=142, replaced=0, skipped=0)
</code></pre>
<p>The <code>CrawlScope</code> dataclass defines the traversal policy: root URLs,
include/exclude patterns, depth limits, and page count caps. The crawler
handles the mechanics of fetching and converting pages to Markdown,
while the scope tells it where to go. This separation means you can
change the backend (swap <code>WebCrawler</code> for <code>CloudflareCrawler</code>, for
instance) without redefining the scope, and vice versa.</p>
<p>Three concrete crawlers ship with <code>v0.2</code>. <code>DirectoryCrawler</code> walks local
file trees and converts supported formats to Markdown. <code>WebCrawler</code>
fetches pages over HTTP using <code>requests</code> and converts them locally.
<code>CloudflareCrawler</code> delegates both fetching and rendering to
Cloudflare&rsquo;s Browser Rendering API (the right tool for sites that load
content through JavaScript). All three implement the same interface:
<code>origins()</code> to discover pages, <code>fetch_raw()</code> and <code>fetch_markdown()</code> for
single-page access, and <code>markdown_documents()</code> for the full pipeline.</p>
<p>Caching is built into the crawler layer. When you pass <code>cache_dir=True</code>
(or an explicit path), each crawler stores fetched content and converted
Markdown in a flat directory of files with metadata sidecars. On
subsequent runs, cached entries are reused if they are still fresh. For
<code>WebCrawler</code> and <code>CloudflareCrawler</code>, freshness is controlled by
<code>cache_stale_after=</code>, a <code>timedelta</code> that defines how long a cached entry
remains valid. This makes interrupted workflows resumable without any
explicit checkpoint logic: rerun the script and the cache supplies
everything that was already fetched, while only new or stale pages
trigger network requests.</p>
<p>Concurrency operates on both sides of this boundary independently. The
crawler can fetch and convert pages in parallel (controlled by
<code>max_workers=</code> on the crawler constructor), and <code>store.ingest()</code> can
write to the store concurrently (controlled by its own <code>max_workers=</code>
argument). For <code>WebCrawler</code>, the breadth-first frontier is explored
concurrently while preserving stable output order, so results come back
in a consistent sequence regardless of which pages respond first.</p>
<h2 id="cloudflarecrawler"><code>CloudflareCrawler</code>
</h2>
<p>The <code>CloudflareCrawler</code> moves the work of a crawl off the local machine.
Instead of fetching pages and converting them to Markdown with local
processes, it hands both jobs to Cloudflare&rsquo;s Browser Rendering API, so
a long crawl over a large site runs on Cloudflare&rsquo;s distributed
infrastructure rather than competing for local CPU and bandwidth. For
collections large enough that concurrent local requests become the
bottleneck, this is the primary reason to reach for it: the slow,
sustained part of building a store happens remotely, and what returns is
ready-to-chunk Markdown. The same arrangement resolves a problem that
defeats a plain HTTP fetch, because the API renders each page in a real
browser, executing JavaScript and waiting for the DOM to settle before
extracting content. Sites built with React, Vue, or Angular, which an
ordinary request reduces to an empty shell, are therefore handled
without extra configuration or a locally installed headless browser.</p>
<p>The usage looks almost identical to <code>WebCrawler</code>, because both share the
same crawl interface. The key difference is that the constructor takes
Cloudflare credentials instead of an HTTP session, and Cloudflare&rsquo;s
infrastructure handles the rendering remotely (so there is no need to
install Playwright, Selenium, or any other local headless browser):</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.crawl</span> <span class="kn">import</span> <span class="n">CloudflareCrawler</span><span class="p">,</span> <span class="n">CrawlScope</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">crawler</span> <span class="o">=</span> <span class="n">CloudflareCrawler</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">account_id</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLOUDFLARE_ACCOUNT_ID&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">api_token</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLOUDFLARE_API_TOKEN&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">cache_dir</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">scope</span> <span class="o">=</span> <span class="n">CrawlScope</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">roots</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;https://my-spa-docs.example.com/&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">depth</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">include_patterns</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;https://my-spa-docs.example.com/**&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">limit</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">documents</span> <span class="o">=</span> <span class="n">crawler</span><span class="o">.</span><span class="n">markdown_documents</span><span class="p">(</span><span class="n">scope</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Iterating the result performs the crawl lazily, yielding one
<code>MarkdownDocument</code> per page. Each document exposes the page&rsquo;s <code>origin</code>
alongside its rendered Markdown <code>content</code>, so a quick pass confirms that
the JavaScript-rendered pages came back with real text rather than the
empty shells a plain HTTP fetch would have produced:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">doc</span> <span class="ow">in</span> <span class="n">documents</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">doc</span><span class="o">.</span><span class="n">origin</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;(</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">doc</span><span class="o">.</span><span class="n">content</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> chars)&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>https://my-spa-docs.example.com/ (3,214 chars)
https://my-spa-docs.example.com/guide/install (5,902 chars)
https://my-spa-docs.example.com/guide/config (8,477 chars)
https://my-spa-docs.example.com/api/reference (12,043 chars)
</code></pre>
<p>The <code>render=True</code> default tells Cloudflare to execute JavaScript before
extracting content. For server-rendered sites where JavaScript execution
is unnecessary, setting <code>render=False</code> reduces crawl time and API usage.
The <code>source=</code> parameter controls how pages are discovered: <code>&quot;all&quot;</code> (the
default) combines multiple discovery methods, <code>&quot;sitemap&quot;</code> reads from the
site&rsquo;s <code>sitemap.xml</code>, <code>&quot;crawl&quot;</code> follows links from the rendered DOM, and
<code>&quot;urls&quot;</code> processes only the explicitly provided roots.</p>
<p>For stores that need regular updates, the <code>modified_since=</code> parameter
restricts the crawl to pages modified after a given Unix timestamp,
keeping refresh jobs lightweight. Combined with the crawl cache and the
store&rsquo;s own deduplication (identical documents are not re-embedded), an
incremental update script can run daily without redundant work:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">timedelta</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">one_week_ago</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span> <span class="o">-</span> <span class="p">(</span><span class="mi">7</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">crawler</span> <span class="o">=</span> <span class="n">CloudflareCrawler</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">account_id</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLOUDFLARE_ACCOUNT_ID&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">api_token</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLOUDFLARE_API_TOKEN&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">cache_dir</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">cache_stale_after</span><span class="o">=</span><span class="n">timedelta</span><span class="p">(</span><span class="n">days</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">modified_since</span><span class="o">=</span><span class="n">one_week_ago</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>The tradeoff is cost: <code>CloudflareCrawler</code> requires a Cloudflare account
with Browser Rendering access. For static HTML sites where a plain HTTP
fetch returns the full content, <code>WebCrawler</code> remains the simpler and
free option. Both crawlers share the same interface, so switching
between them requires only a constructor change.</p>
<h2 id="postgresql-store">PostgreSQL store
</h2>
<p>raghilda <code>v0.1</code> shipped with three store backends: DuckDB (local,
zero-config), ChromaDB, and OpenAI Vector Stores. <code>v0.2</code> adds
<code>PostgreSQLStore</code>, backed by <code>psycopg2</code> and <code>pgvector</code>. This is the
natural choice for production deployments where the store needs to be
shared across services, or where you already have PostgreSQL
infrastructure.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.store</span> <span class="kn">import</span> <span class="n">PostgreSQLStore</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.embedding</span> <span class="kn">import</span> <span class="n">EmbeddingOpenAI</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">PostgreSQLStore</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">connection</span><span class="o">=</span><span class="s2">&#34;postgresql://user:pass@localhost:5432/mydb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">embed</span><span class="o">=</span><span class="n">EmbeddingOpenAI</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="o">=</span><span class="s2">&#34;docs_store&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">overwrite</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>The store supports full-text search via PostgreSQL&rsquo;s native
<code>tsvector</code>/<code>tsquery</code> with a pre-computed column and GIN index, vector
similarity search via pgvector with HNSW indexes (supporting cosine, L2,
and inner product distance metrics), and combined retrieval that merges
both result sets with deoverlap support.</p>
<p>Retrieval uses the same interface as every other backend: a single
<code>retrieve()</code> call returns a ranked list of chunks, each carrying its
similarity score under <code>metrics</code> and its heading-hierarchy <code>context</code>.
Running a query against a store populated with the raghilda
documentation returns the most relevant chunks first:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="n">store</span><span class="o">.</span><span class="n">retrieve</span><span class="p">(</span><span class="s2">&#34;How are vector indexes configured?&#34;</span><span class="p">,</span> <span class="n">top_k</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Score: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">metrics</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">value</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;---&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>Score: 0.5142
# PostgreSQL store
Vector similarity search runs through pgvector with HNSW indexes,
supporting cosine, L2, and inner product distance metrics. Call
build_index() after ingestion to create them.
---
Score: 0.4417
# PostgreSQL store &gt; Combined retrieval
Combined retrieval merges the vector and full-text result sets and
applies deoverlap to drop redundant overlapping chunks, returning a
single ranked list from one retrieve() call.
---
</code></pre>
<p>Attributes work as expected: scalar types map to columns, struct types
map to JSONB, and attribute filters can query into JSONB fields using
the <code>-&gt;&gt;</code> operator. The <code>build_index()</code> method creates HNSW indexes
after ingestion, and the <code>vss_index=</code> parameter on <code>create()</code> controls
the default index type.</p>
<p>Connection strings are accepted directly in <code>create()</code> and <code>connect()</code>,
so you can point the store at any PostgreSQL instance with pgvector
installed. If the pgvector extension is missing, the store raises an
informative error rather than failing cryptically on the first vector
operation.</p>
<h2 id="nvidia-nim-embeddings">NVIDIA NIM embeddings
</h2>
<p>The embedding layer gains a new provider: <code>EmbeddingNVIDIA</code>, which
connects to NVIDIA&rsquo;s OpenAI-compatible embedding API. The default model
is <code>nvidia/llama-nemotron-embed-1b-v2</code>, a compact embedding model
suitable for retrieval workloads. The provider reads its API key from
the <code>NVIDIA_API_KEY</code> environment variable.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">raghilda.embedding</span> <span class="kn">import</span> <span class="n">EmbeddingNVIDIA</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">embedding</span> <span class="o">=</span> <span class="n">EmbeddingNVIDIA</span><span class="p">()</span></span></span></code></pre></div></div>
<p>One notable feature of NVIDIA&rsquo;s embedding API is differentiated input
types: queries and documents are embedded with different prefixes
(<code>&quot;query&quot;</code> and <code>&quot;passage&quot;</code>), which can improve retrieval quality for
asymmetric search where the query is short and the documents are long.
raghilda handles this distinction automatically when the store calls the
embedding provider during ingestion and retrieval.</p>
<p>The provider includes built-in rate limit handling with exponential
backoff. NVIDIA&rsquo;s 429 responses carry no <code>Retry-After</code> header or rate
limit metadata, so backoff is the only viable strategy. For users who
want to keep embedding computation entirely local, NVIDIA NIM can also
be self-hosted, in which case you point the provider at your local
endpoint with a <code>base_url=</code> override.</p>
<h2 id="improved-duckdb-error-messages">Improved DuckDB error messages
</h2>
<p>A smaller but practical improvement: <code>DuckDBStore</code> now raises clear,
actionable errors when BM25 retrieval is attempted before the index has
been built, or after writes have made the index stale. Previously, this
produced a cryptic <code>CatalogException</code> from DuckDB about a missing
<code>match_bm25</code> function. The new message tells you exactly what to do:</p>
<pre><code>RuntimeError: DuckDBStore retrieval requires a current BM25 index.
Call `store.build_index(&quot;bm25&quot;)` after inserting or updating documents
and before calling `retrieve_bm25()` or `retrieve()`.
</code></pre>
<p>The store now tracks BM25 freshness internally: the index is marked
stale after any <code>upsert()</code> call and marked current after
<code>build_index(&quot;bm25&quot;)</code>. This tracking happens off the retrieval hot path,
so there is no per-query overhead. HNSW indexes are unaffected because
DuckDB maintains them across writes automatically.</p>
<h2 id="why-even-use-raghilda">Why even use raghilda?
</h2>
<p>raghilda is a retrieval library, not an orchestration framework. Larger
projects like LangChain and LlamaIndex offer composable retrieval
components too, but they also ship agent runtimes, chain abstractions,
prompt management, and memory systems. If all you need is the retrieval
pipeline (crawl, chunk, embed, store, retrieve), raghilda gives you that
without the surrounding framework. The API surface is small: plain
dataclasses, iterators, and direct function calls. There are fewer
layers of indirection between your code and the underlying operations,
which makes the pipeline easier to debug and reason about.</p>
<p>raghilda <code>v0.2</code> makes that focused scope practical at scale. The crawl
API adds caching and concurrency while keeping each step a separate,
inspectable call. The storage layer lets you start with a local DuckDB
file and move to PostgreSQL or OpenAI Vector Stores later without
changing retrieval code. And every backend provides hybrid retrieval
(semantic search, BM25, and attribute filtering combined in a single
<code>retrieve()</code> call) out of the box, without assembling separate retriever
classes or configuring a pipeline graph.</p>
<h2 id="getting-started">Getting started
</h2>
<p>raghilda <code>v0.2</code> is available now on PyPI (<code>pip install raghilda</code>). The
<a href="https://posit-dev.github.io/raghilda/" target="_blank" rel="noopener">raghilda documentation site</a>
covers all of the features described here in more detail. The <a href="https://posit-dev.github.io/raghilda/user-guide/getting-started.html" target="_blank" rel="noopener">Getting
Started</a>
guide walks through building a store from scratch, and the <a href="https://posit-dev.github.io/raghilda/user-guide/crawling-and-ingestion.html" target="_blank" rel="noopener">Crawling and
Ingestion</a>
guide covers the new crawl API in depth. A dedicated
<a href="https://posit-dev.github.io/raghilda/user-guide/cloudflare-crawler.html" target="_blank" rel="noopener">CloudflareCrawler</a>
guide explains browser rendering, page discovery, caching, and
incremental updates. The <a href="https://github.com/posit-dev/raghilda" target="_blank" rel="noopener">GitHub
repository</a> has the source, issue
tracker, and full changelog. If you run into problems or have feature
requests, open an issue there.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-01_raghilda-0-2-0/assets/raghilda-updated.png" length="1954675" type="image/png" />
    </item>
    <item>
      <title>Great Tables `v0.22.0`: footnotes, summary rows, column merges, and more</title>
      <link>https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/</guid>
      <dc:creator>Rich Iannone</dc:creator><description><![CDATA[<p><a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">Great Tables</a> gives you a
grammar for assembling presentation-quality display tables in Python.
You start from a DataFrame, declare which parts of the table mean what
(the stub, the row groups, the column labels), and then layer on
formatting, styling, and annotation until the table communicates exactly
what you intend. The library has always taken much of its design from
the <a href="https://gt.rstudio.com" target="_blank" rel="noopener"><strong>gt</strong> R package</a>, and over successive
releases the Python version has been steadily catching up to the
capabilities that R users have enjoyed for years.</p>
<p>The <code>v0.22.0</code> release is the largest step in that direction so far. It
introduces footnotes, group-wise summary rows, a family of
column-merging methods, a suite of text transformations, several
value-substitution helpers, two new formatting methods, and a modern
image-export pipeline through <code>gtsave()</code>. The LaTeX output gained the
ability to render stubs and row groups, and Pandas is no longer a
required dependency. There is a great deal to cover, so this post walks
through each addition in turn, with a small working example for every
one.</p>
<h2 id="footnotes-with-tab_footnote">Footnotes with <code>tab_footnote()</code>
</h2>
<p>Footnotes are one of the oldest conventions in tabular presentation, and
they solve a real problem: sometimes a value, a label, or a heading
needs a short explanation that would clutter the table if placed inline.
The new <code>tab_footnote()</code> method attaches a footnote to any location in
the table and manages the marks for you, numbering them sequentially in
the order they appear and collecting the notes themselves in the table&rsquo;s
footer.</p>
<p>A location is specified with one of the <code>loc.*</code> helpers, the same ones
used elsewhere in the library for styling. You can attach a note to
cells in the stub, to a column label, to the subtitle, or to body cells.
Because the footnote text accepts <code>md()</code> and <code>html()</code>, you can format it
with Markdown or raw HTML just as you would any other piece of table
content.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span><span class="p">,</span> <span class="n">loc</span><span class="p">,</span> <span class="n">md</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables.data</span> <span class="kn">import</span> <span class="n">towny</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">towny_mini</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">pl</span><span class="o">.</span><span class="n">from_pandas</span><span class="p">(</span><span class="n">towny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;csd_type&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;city&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">select</span><span class="p">([</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;density_2021&#34;</span><span class="p">,</span> <span class="s2">&#34;population_2021&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">top_k</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="n">by</span><span class="o">=</span><span class="s2">&#34;population_2021&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="s2">&#34;population_2021&#34;</span><span class="p">,</span> <span class="n">descending</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">towny_mini</span><span class="p">,</span> <span class="n">rowname_col</span><span class="o">=</span><span class="s2">&#34;name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="o">=</span><span class="n">md</span><span class="p">(</span><span class="s2">&#34;The 10 Largest Municipalities in `towny`&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">subtitle</span><span class="o">=</span><span class="s2">&#34;Population values taken from the 2021 census.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_integer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_label</span><span class="p">(</span><span class="n">density_2021</span><span class="o">=</span><span class="s2">&#34;Density&#34;</span><span class="p">,</span> <span class="n">population_2021</span><span class="o">=</span><span class="s2">&#34;Population&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_footnote</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">footnote</span><span class="o">=</span><span class="s2">&#34;Part of the Greater Toronto Area.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">stub</span><span class="p">(</span><span class="n">rows</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Toronto&#34;</span><span class="p">,</span> <span class="s2">&#34;Mississauga&#34;</span><span class="p">,</span> <span class="s2">&#34;Brampton&#34;</span><span class="p">,</span> <span class="s2">&#34;Markham&#34;</span><span class="p">,</span> <span class="s2">&#34;Vaughan&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">]),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_footnote</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">footnote</span><span class="o">=</span><span class="n">md</span><span class="p">(</span><span class="s2">&#34;Density is in terms of persons per {{km^2}}.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">column_labels</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;density_2021&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_footnote</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">footnote</span><span class="o">=</span><span class="s2">&#34;Census results made public on February 9, 2022.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">subtitle</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">opt_footnote_marks</span><span class="p">(</span><span class="n">marks</span><span class="o">=</span><span class="s2">&#34;letters&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/footnotes.png" alt="A towny table with three lettered footnotes attached to the subtitle, a column label, and several stub cells"></p>
<p>The marks themselves are configurable through <code>opt_footnote_marks()</code>.
The default is a standard set of typographic symbols, but you can switch
to numbers or letters, as we did above with <code>marks=&quot;letters&quot;</code>. The
<code>placement=</code> argument on <code>tab_footnote()</code> controls whether a mark sits
to the left or right of the cell content, and the default <code>&quot;auto&quot;</code>
chooses a side based on the cell&rsquo;s alignment.</p>
<h2 id="group-wise-summaries-with-summary_rows">Group-wise summaries with <code>summary_rows()</code>
</h2>
<p>When a table is divided into row groups, readers frequently want a
per-group summary: a total, a mean, a minimum and maximum. The
<code>summary_rows()</code> method computes these and inserts them as labeled rows
within each group, either at the bottom (the default) or at the top.</p>
<p>The aggregations are described with the <code>fns=</code> argument, a dictionary
whose keys become the row labels and whose values are the expressions to
evaluate. The expressions can be Polars expressions, which is the most
concise option when your data is a Polars DataFrame, or plain Python
callables that receive a DataFrame subset. A formatting function from
the <code>vals.*</code> family can be passed through <code>fmt=</code> so that the summary
values match the formatting of the rest of the table.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span><span class="p">,</span> <span class="n">vals</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables.data</span> <span class="kn">import</span> <span class="n">gtcars</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gtcars_mini</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">pl</span><span class="o">.</span><span class="n">from_pandas</span><span class="p">(</span><span class="n">gtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">select</span><span class="p">([</span><span class="s2">&#34;mfr&#34;</span><span class="p">,</span> <span class="s2">&#34;model&#34;</span><span class="p">,</span> <span class="s2">&#34;hp&#34;</span><span class="p">,</span> <span class="s2">&#34;trq&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">head</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">gtcars_mini</span><span class="p">,</span> <span class="n">rowname_col</span><span class="o">=</span><span class="s2">&#34;model&#34;</span><span class="p">,</span> <span class="n">groupname_col</span><span class="o">=</span><span class="s2">&#34;mfr&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">summary_rows</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">fns</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Min&#34;</span><span class="p">:</span> <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;hp&#34;</span><span class="p">,</span> <span class="s2">&#34;trq&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">min</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Max&#34;</span><span class="p">:</span> <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;hp&#34;</span><span class="p">,</span> <span class="s2">&#34;trq&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">max</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">fmt</span><span class="o">=</span><span class="n">vals</span><span class="o">.</span><span class="n">fmt_integer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/summary-rows.png" alt="A gtcars table grouped by manufacturer, with Min and Max summary rows beneath each group"></p>
<p>By default the summary applies to every group, but the <code>groups=</code>
argument narrows it to a named subset when you only need summaries in
certain places. The release also includes <code>grand_summary_rows()</code>, a
companion method that produces a single summary across the entire table
rather than one per group. Both kinds of summary can be targeted for
styling through <code>loc.summary()</code> and <code>loc.grand_summary()</code>, so you can
shade them or set them apart from the regular body rows.</p>
<h2 id="merging-columns-together">Merging columns together
</h2>
<p>Tables often hold several columns that, conceptually, describe a single
quantity. A value and its uncertainty, the lower and upper ends of a
range, or a count paired with its percentage all read better as one
column than as two. The release adds a family of merge methods for
exactly these situations, along with a generic method for everything
else.</p>
<p>The most specialized of these is <code>cols_merge_uncert()</code>, which combines a
measured value with its uncertainty and renders the pair with a
plus-or-minus separator. You provide the value column and the
uncertainty column, and the second column is hidden automatically once
it has been folded into the first.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables.data</span> <span class="kn">import</span> <span class="n">exibble</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">exibble_mini</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">pl</span><span class="o">.</span><span class="n">from_pandas</span><span class="p">(</span><span class="n">exibble</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">select</span><span class="p">(</span><span class="s2">&#34;num&#34;</span><span class="p">,</span> <span class="s2">&#34;currency&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">exibble_mini</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_number</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;num&#34;</span><span class="p">,</span> <span class="n">decimals</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">use_seps</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_merge_uncert</span><span class="p">(</span><span class="n">col_val</span><span class="o">=</span><span class="s2">&#34;currency&#34;</span><span class="p">,</span> <span class="n">col_uncert</span><span class="o">=</span><span class="s2">&#34;num&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_label</span><span class="p">(</span><span class="n">currency</span><span class="o">=</span><span class="s2">&#34;value + uncert.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/cols-merge-uncert.png" alt="A table merging a value column and an uncertainty column into a single value-plus-uncertainty column"></p>
<p>The <code>cols_merge_range()</code> method works the same way for a pair of columns
that mark the beginning and end of a range, joining them with an en dash
by default (the separator is adjustable through <code>sep=</code>). The
<code>cols_merge_n_pct()</code> method pairs a count with a percentage, rendering
values in the familiar <code>10 (16.70%)</code> form and suppressing the percentage
when the count is zero.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">df</span> <span class="o">=</span> <span class="n">pl</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;category&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;n&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;pct&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">0.167</span><span class="p">,</span> <span class="mf">0.333</span><span class="p">,</span> <span class="mf">0.500</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">df</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_percent</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;pct&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_merge_n_pct</span><span class="p">(</span><span class="n">col_n</span><span class="o">=</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="n">col_pct</span><span class="o">=</span><span class="s2">&#34;pct&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_label</span><span class="p">(</span><span class="n">n</span><span class="o">=</span><span class="s2">&#34;Count (%)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/cols-merge-n-pct.png" alt="A table merging a count column and a percentage column into a single Count (%) column"></p>
<p>For anything that does not fit those three patterns there is the generic
<code>cols_merge()</code>, which takes a list of columns and a <code>pattern=</code> template.
The template uses zero-based indices in braces to refer to the columns,
so a pattern of <code>&quot;{0} to {1}&quot;</code> interleaves the first and second columns
with the literal text between them. The first column named becomes the
visible, merged column, and the rest are hidden by default. This is the
general mechanism on which the specialized methods are built, and it is
the right tool when your desired arrangement is unusual.</p>
<p>A related convenience is <code>cols_reorder()</code>, which rearranges every column
in a single call. Previously, a full reordering meant a sequence of
<code>cols_move_*()</code> invocations; now you can list the columns in the order
you want and have the table laid out accordingly. The method expects
every column to appear exactly once, raising an error if any are omitted
or duplicated, which guards against the silent loss of a column.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables.data</span> <span class="kn">import</span> <span class="n">exibble</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">exibble_mini</span> <span class="o">=</span> <span class="n">exibble</span><span class="p">[[</span><span class="s2">&#34;num&#34;</span><span class="p">,</span> <span class="s2">&#34;char&#34;</span><span class="p">,</span> <span class="s2">&#34;fctr&#34;</span><span class="p">,</span> <span class="s2">&#34;date&#34;</span><span class="p">,</span> <span class="s2">&#34;time&#34;</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">exibble_mini</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_reorder</span><span class="p">([</span><span class="s2">&#34;fctr&#34;</span><span class="p">,</span> <span class="s2">&#34;date&#34;</span><span class="p">,</span> <span class="s2">&#34;time&#34;</span><span class="p">,</span> <span class="s2">&#34;char&#34;</span><span class="p">,</span> <span class="s2">&#34;num&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/cols-reorder.png" alt="An exibble table with its columns rearranged into a custom order"></p>
<h2 id="a-suite-of-text-transformations">A suite of text transformations
</h2>
<p>Formatting methods handle numbers, dates, and currencies, but cell
content sometimes needs a transformation that no formatter anticipates.
The release introduces four <code>text_*()</code> methods that operate on the
rendered text of cells, each addressing a different shape of problem.</p>
<p>The most general is <code>text_transform()</code>, which applies an arbitrary
function to the text of the targeted cells. The function receives the
cell&rsquo;s current string and returns a new one, which makes it suitable for
any transformation you can express in Python. Because it runs after
formatting, you can format a value first and then decorate the result.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span><span class="p">,</span> <span class="n">loc</span><span class="p">,</span> <span class="n">exibble</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">exibble</span><span class="p">[[</span><span class="s2">&#34;num&#34;</span><span class="p">,</span> <span class="s2">&#34;char&#34;</span><span class="p">]]</span><span class="o">.</span><span class="n">head</span><span class="p">(</span><span class="mi">4</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_number</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;num&#34;</span><span class="p">,</span> <span class="n">decimals</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">text_transform</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="p">[</span><span class="n">loc</span><span class="o">.</span><span class="n">body</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;num&#34;</span><span class="p">),</span> <span class="n">loc</span><span class="o">.</span><span class="n">body</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;char&#34;</span><span class="p">)],</span>
</span></span><span class="line"><span class="cl">        <span class="n">fn</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;~ </span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/text-transform.png" alt="An exibble table with a tilde prefix applied to the num and char cells"></p>
<p>When the transformation is a regular-expression substitution,
<code>text_replace()</code> is more direct. It takes a <code>pattern=</code> and a
<code>replacement=</code>, and it supports capture groups, so you can wrap or
rearrange matched text. The example below finds parenthetical text and
emphasizes it with HTML tags.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span><span class="p">,</span> <span class="n">loc</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;item&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Column A (details)&#34;</span><span class="p">,</span> <span class="s2">&#34;Column B (info)&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;value&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">df</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">text_replace</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pattern</span><span class="o">=</span><span class="sa">r</span><span class="s2">&#34;\((.+?)\)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">replacement</span><span class="o">=</span><span class="sa">r</span><span class="s2">&#34;(&lt;em&gt;\1&lt;/em&gt;)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">body</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;item&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/text-replace.png" alt="A table with parenthetical text emphasized in the item column"></p>
<p>The remaining two methods cover conditional replacement.
<code>text_case_match()</code> is a switch-like construct: each case is a tuple
pairing one or more values to match against a replacement string, with
an optional <code>default=</code> for everything unmatched. <code>text_case_when()</code>
generalizes this to predicates, where each case pairs a function that
returns a boolean with the replacement to use when it is true. The case
ordering matters, since the first matching predicate wins, which makes
it a natural fit for binning a numeric column into labels.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span><span class="p">,</span> <span class="n">loc</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s2">&#34;score&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">95</span><span class="p">,</span> <span class="mi">72</span><span class="p">,</span> <span class="mi">88</span><span class="p">,</span> <span class="mi">61</span><span class="p">,</span> <span class="mi">100</span><span class="p">]})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">df</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_number</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;score&#34;</span><span class="p">,</span> <span class="n">decimals</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">text_case_when</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">90</span><span class="p">,</span> <span class="s2">&#34;A&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">80</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">70</span><span class="p">,</span> <span class="s2">&#34;C&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">default</span><span class="o">=</span><span class="s2">&#34;F&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">body</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;score&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/text-case-when.png" alt="A score column whose numeric values are replaced by letter grades"></p>
<h2 id="substituting-specific-values">Substituting specific values
</h2>
<p>Closely related to text transformation is the act of replacing
particular values for the sake of readability. A column of measurements
might contain values too small to be meaningful, or zeros that would be
better shown as a dash, or missing entries that should read as something
other than a blank. The release adds a family of <code>sub_*()</code> methods for
these cases: <code>sub_missing()</code> for missing values, <code>sub_zero()</code> for zeros,
<code>sub_small_vals()</code> and <code>sub_large_vals()</code> for values beyond a threshold,
and the general <code>sub_values()</code> for replacing any specified value.</p>
<p>The small-value substitution is representative. It replaces values whose
magnitude falls below a <code>threshold=</code> with a chosen pattern, which is
useful when very small numbers carry no real information and only
distract. The <code>sign=</code> argument restricts the substitution to positive or
negative values, so you can treat the two tails of a distribution
differently.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">neg_vals_df</span> <span class="o">=</span> <span class="n">pl</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;i&#34;</span><span class="p">:</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">6</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;numbers&#34;</span><span class="p">:</span> <span class="p">[</span><span class="o">-</span><span class="mf">0.0001</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.005</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.05</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">100.0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">neg_vals_df</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_number</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;numbers&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">sub_small_vals</span><span class="p">(</span><span class="n">sign</span><span class="o">=</span><span class="s2">&#34;-&#34;</span><span class="p">,</span> <span class="n">threshold</span><span class="o">=</span><span class="mf">0.01</span><span class="p">,</span> <span class="n">small_pattern</span><span class="o">=</span><span class="s2">&#34;~0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/sub-small-vals.png" alt="A column of small-magnitude negative numbers replaced by an approximate-zero marker"></p>
<p>These methods operate on the underlying values rather than on rendered
text, so they compose cleanly with the formatting methods. You decide
what counts as missing, zero, small, or large, and the table presents
those cases consistently wherever they occur.</p>
<h2 id="two-new-formatters-durations-and-parts-per">Two new formatters: durations and parts-per
</h2>
<p>The formatting family gained two members. The first, <code>fmt_duration()</code>,
renders durations in any of several styles. Numeric inputs are
interpreted according to an <code>input_units=</code> setting (seconds, minutes,
hours, days, or weeks), while Polars <code>Duration</code> columns are detected
automatically. The <code>duration_style=</code> argument selects between a narrow
style such as <code>5d 3h</code>, a wide style such as <code>5 days, 3 hours</code>, a
colon-separated style such as <code>02:15:30</code>, and ISO 8601. The example
below renders race times as zero-padded <code>HH:MM:SS</code>.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Marathon&#34;</span><span class="p">,</span> <span class="s2">&#34;Half Marathon&#34;</span><span class="p">,</span> <span class="s2">&#34;10K&#34;</span><span class="p">,</span> <span class="s2">&#34;Mile&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;winning_time_s&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">7377</span><span class="p">,</span> <span class="mi">3542</span><span class="p">,</span> <span class="mi">1620</span><span class="p">,</span> <span class="mi">233</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">df</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_duration</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="s2">&#34;winning_time_s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">input_units</span><span class="o">=</span><span class="s2">&#34;seconds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">duration_style</span><span class="o">=</span><span class="s2">&#34;colon-sep&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">output_units</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;hours&#34;</span><span class="p">,</span> <span class="s2">&#34;minutes&#34;</span><span class="p">,</span> <span class="s2">&#34;seconds&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/fmt-duration.png" alt="Race times formatted as zero-padded hours, minutes, and seconds"></p>
<p>The second formatter, <code>fmt_partsper()</code>, handles parts-per quantities:
per-mille, parts per million, parts per billion, and finer scales still.
The <code>to_units=</code> argument names the target quantity, the values are
scaled to match unless you opt out with <code>scale_values=False</code>, and the
symbol is rendered appropriately for both HTML and LaTeX output. The
example formats gas concentrations as parts per billion by volume.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">concentrations</span> <span class="o">=</span> <span class="n">pl</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;gas&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;CO&#34;</span><span class="p">,</span> <span class="s2">&#34;NO2&#34;</span><span class="p">,</span> <span class="s2">&#34;O3&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;conc&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">1.5</span><span class="p">,</span> <span class="mf">35.0</span><span class="p">,</span> <span class="mf">120.0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">concentrations</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_partsper</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="s2">&#34;conc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">to_units</span><span class="o">=</span><span class="s2">&#34;ppb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">scale_values</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">symbol</span><span class="o">=</span><span class="s2">&#34;ppbV&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/fmt-partsper.png" alt="Gas concentrations formatted as parts per billion by volume"></p>
<h2 id="saving-tables-as-images-with-gtsave">Saving tables as images with <code>gtsave()</code>
</h2>
<p>A display table is often destined for a slide deck, a report, or a
README, and in those settings you need an image rather than live HTML.
The new <code>gtsave()</code> method produces one by rendering the table in a
headless instance of Chrome and capturing it. It writes PNG, JPEG, WebP,
and PDF, choosing the format from the file extension you supply.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables.data</span> <span class="kn">import</span> <span class="n">gtcars</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gtcars_mini</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">pl</span><span class="o">.</span><span class="n">from_pandas</span><span class="p">(</span><span class="n">gtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">select</span><span class="p">([</span><span class="s2">&#34;mfr&#34;</span><span class="p">,</span> <span class="s2">&#34;model&#34;</span><span class="p">,</span> <span class="s2">&#34;msrp&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">head</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">gtcars_mini</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_header</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Some Cars from gtcars&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_currency</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;msrp&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">gtsave</span><span class="p">(</span><span class="s2">&#34;my_table.png&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p style="text-align: center;"><img src="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/gtsave-example.png" alt="A small gtcars table with a title and currency-formatted MSRP, saved to a PNG with gtsave()"></p>
<p>Several arguments control the capture. The <code>zoom=</code> factor governs the
resolution of raster output, with higher values producing sharper
images, while <code>expand=</code> adds padding around the table and
<code>vwidth=</code>/<code>vheight=</code> set the viewport. The <code>gtsave()</code> method replaces
the older <code>save()</code>, which is now deprecated; existing code will continue
to work for the time being, but new work should use <code>gtsave()</code>.</p>
<h2 id="better-latex-output">Better LaTeX output
</h2>
<p>Great Tables can render to LaTeX as well as HTML, and that path received
substantial attention in this release. LaTeX output now supports the
stub and row groups, including spanning column headers and the
row-group-as-column layout, which means that tables relying on these
structural features are no longer limited to HTML. In addition, Markdown
and HTML content placed in cells, headers, or footnotes is now converted
to its LaTeX equivalent during rendering, so styled text survives the
trip into a LaTeX document rather than appearing as literal markup. For
anyone producing tables destined for a paper or a typeset report, the
LaTeX output is now much closer in capability to the HTML output.</p>
<h2 id="polars-without-pandas">Polars without Pandas
</h2>
<p>Until now, Great Tables required Pandas even if all of your work was in
Polars. As of this release, Pandas is an optional dependency, and the
library is fully functional with Polars alone. For Polars-first projects
and for lightweight environments where every dependency counts, this
removes a sizable transitive install that was not actually needed.
Pandas users are unaffected: a DataFrame from either library works as
input exactly as before, and the choice of backend remains yours.</p>
<h2 id="getting-started">Getting started
</h2>
<p>Great Tables <code>v0.22.0</code> is available now on PyPI, so a
<code>pip install great-tables</code> (or an upgrade of an existing install) brings
everything described here. The <a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">documentation
site</a>, which moved to <a href="https://posit-dev.github.io/great-docs/" target="_blank" rel="noopener">Great
Docs</a> as its generator in this
release, covers each method in detail with runnable examples, and the
<a href="https://posit-dev.github.io/great-tables/get-started/" target="_blank" rel="noopener">User Guide</a> has
been updated to reflect the new features. The <a href="https://github.com/posit-dev/great-tables" target="_blank" rel="noopener">GitHub
repository</a> holds the source,
the full changelog, and the issue tracker. This release also welcomed
several first-time contributors, and if you would like to join them, or
simply have a feature to request or a bug to report, the issue tracker
is the place to start.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/assets/great-tables-v-0-22-0-release.png" length="1882664" type="image/png" />
    </item>
    <item>
      <title>Investing in the Future of Interactive Computing: We&#39;ve Joined the Jupyter Foundation</title>
      <link>https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<p>At Posit, our mission is to empower data scientists and researchers with the best possible tools to explore, visualize, and share their work. For years, our team has relied on the <a href="https://jupyter.org/" target="_blank" rel="noopener">Jupyter</a> ecosystem, with Notebooks, IPython, JupyterLab, and JupyterHub all integrated across our product offerings and Jupyter kernels powering <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>&rsquo;s R and Python support. Today, we are proud to announce that we are deepening our commitment to this community by becoming an official <a href="https://jupyterfoundation.org/" target="_blank" rel="noopener">Jupyter Foundation</a> Member!</p>
<p>Since its inception, <a href="https://jupyter.org/" target="_blank" rel="noopener">Project Jupyter</a> has grown into one of the world&rsquo;s most widely used open source ecosystems for interactive computing, powering breakthroughs in research, education, and industry. The Jupyter Foundation, hosted by the <a href="https://training.linuxfoundation.org/" target="_blank" rel="noopener">Linux Foundation</a>, was established to support the long-term sustainability of this work by bringing together organizations committed to investing in the project&rsquo;s future.</p>
<p>By joining as a Foundation Member, we are becoming active stewards of its future. Our membership helps fund the core infrastructure, the release engineering, and the community events that keep Jupyter at the cutting edge of scientific computing. This move reinforces our promise to the open source community: we will not only build on these tools, but we will also build up the people and processes that create them.</p>
<p>Open source is a shared resource, and it requires shared responsibility. We encourage our partners and peers in the industry to join us in supporting the foundations that make our work possible.</p>
<p>To learn more about our commitment to open research and technical innovation, visit our <a href="https://opensource.posit.co/about/posit/" target="_blank" rel="noopener">About Page</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/featured.png" length="392627" type="image/png" />
    </item>
    <item>
      <title>Ask more of your dashboard with querychat and ggsql</title>
      <link>https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/</link>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/</guid>
      <dc:creator>Carson Sievert</dc:creator><description><![CDATA[<p>I&rsquo;m super excited to announce that <a href="https://posit-dev.github.io/querychat/" target="_blank" rel="noopener">querychat</a> now supports <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release">ggsql</a>. As a result, querychat users can now go beyond predetermined dashboard views and ask the LLM for bespoke visualizations on the fly. With this addition, querychat now comes with three pre-built tools that LLMs can use in response to user questions:</p>
<ol>
<li><strong>Visualize:</strong> execute <code>ggsql</code> queries, rendered as visualizations in the chat.</li>
<li><strong>Query:</strong> execute SQL queries, rendered as tables/text in the chat.</li>
<li><strong>Filter:</strong> execute filter SQL queries on predetermined views, allowing the LLM to reactively drill into the data in response to user questions.</li>
</ol>
<p>Equipped with these tools, your dashboard can offer a balance between &ldquo;curated insights&rdquo; and &ldquo;explorable data&rdquo; &mdash; surfacing interesting trends and summaries upfront, but also letting users ask follow-up questions that go beyond the predetermined views. The video below gives you a feel for this workflow in action<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Note how the conversation starts off by filtering the predetermined views, but then progresses into more general exploration.</p>
<script src="https://fast.wistia.com/player.js" async></script>
<script src="https://fast.wistia.com/embed/at01coepkh.js" async type="module"></script>
<style>wistia-player[media-id='at01coepkh']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/at01coepkh/swatch'); display: block; filter: blur(5px); padding-top:58.76%; }</style>
<p><wistia-player media-id="at01coepkh" aspect="1.701851851851852"></wistia-player></p>
<p>Crucially, every one of <code>querychat</code>&rsquo;s tools works by executing SQL (or <code>ggsql</code>) &mdash; never arbitrary code. Every query is also fully visible and reproducible: users can inspect the code behind any response, and copy it to run independently. In the next version of <code>querychat</code>, you can also expect to see a rich export capability, allowing you to save the insights you care about in a format you care about (e.g. a Quarto dashboard, Jupyter notebook, or R Markdown file).</p>
<p>Restricting <code>querychat</code>&rsquo;s code execution capability to SQL (or <code>ggsql</code>) is an intentional design choice. We can guarantee that querychat does not run arbitrary R or python code, making it viable for production environments where security and control are required. As a result, <code>querychat</code> may not be as capable as a general coding assistant like <a href="https://posit-dev.github.io/assistant" target="_blank" rel="noopener">Posit Assistant</a>, but you generally don&rsquo;t want production apps that let users execute arbitrary code. By focusing on SQL execution, <code>querychat</code> can maintain a strong security posture while still enabling useful exploration of data via natural language.</p>
<h2 id="get-started">Get started
</h2>
<p>Getting started is as easy as pointing <code>QueryChat()</code> at your data source and getting set up with an LLM. Although we recommend frontier models for an ideal experience, open-weight models have recently become <a href="https://simonpcouch.com/blog/2026-04-16-local-agents-2/" target="_blank" rel="noopener">quite capable</a>, and can be a great way to get started without needing API access or incurring costs.</p>
<p>For this article, I&rsquo;m using <a href="https://lmstudio.ai/" target="_blank" rel="noopener">LMStudio</a> to run <code>google/gemma-4-26b-a4b</code> locally on my fairly standard MacBook Pro. If you prefer a different LLM or provider, refer to the <a href="https://posit-dev.github.io/querychat/py/models.html" target="_blank" rel="noopener">docs</a> (<a href="https://posit-dev.github.io/querychat/r/articles/models.html" target="_blank" rel="noopener">R</a>) to see how to configure.</p>
<p>Once you&rsquo;ve picked an LLM, next step is ensuring both <code>querychat</code> and <code>ggsql</code> are installed. Here we&rsquo;ll also use the <code>palmerpenguins</code> data set to keep examples lightweight and self-contained, but you can point <code>querychat</code> at anything from a data frame to a <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">remote database connection</a> (<a href="https://posit-dev.github.io/querychat/r/articles/data-sources.html" target="_blank" rel="noopener">R</a>).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Python</a></li>
<li><a href="#tabset-1-2">R</a></li>
</ul>
<div id="tabset-1-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;querychat[viz]&#34;</span> palmerpenguins</span></span></code></pre></div></div>
</div>
<div id="tabset-1-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;querychat&#34;</span><span class="p">,</span> <span class="s">&#34;ggsql&#34;</span><span class="p">,</span> <span class="s">&#34;palmerpenguins&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
</div>
</div>
<h2 id="basic-usage">Basic usage
</h2>
<p>Once installed, you can get a basic chat UI using the code below. We&rsquo;ll include just the <code>query</code> and <code>visualize</code> tools here since the <code>filter</code> tool is designed to work in the context of a larger dashboard, which we&rsquo;ll cover in the next section.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">Python</a></li>
<li><a href="#tabset-2-2">R</a></li>
</ul>
<div id="tabset-2-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-2">
  <div class="code-with-filename-label" id="code-filename-2"><span class="font-mono text-sm">app.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span> 
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;query&#34;</span><span class="p">,</span> <span class="s2">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">ui</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div></div>
<p>With <code>app.py</code> saved locally and your LLM running, start the app with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">shiny run app.py</span></span></code></pre></div></div>
</div>
<div id="tabset-2-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-4">
  <div class="code-with-filename-label" id="code-filename-4"><span class="font-mono text-sm">app.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">palmerpenguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tools</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;query&#34;</span><span class="p">,</span> <span class="s">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="n">bslib</span><span class="o">::</span><span class="nf">page_fillable</span><span class="p">(</span><span class="n">qc</span><span class="o">$</span><span class="nf">ui</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
<p>With <code>app.R</code> saved locally and your LLM running, start the app with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">runApp</span><span class="p">(</span><span class="s">&#34;app.R&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Upon opening the app, the LLM greets us with a welcome message tailored to the data source we&rsquo;ve provided:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-chat.png" alt="Basic QueryChat interface to the Palmer Penguins dataset" class="shadow rounded" />
<h3 id="query-tool">Query tool
</h3>
<p>For questions addressable via numerical summaries, the LLM requests the <code>query</code> tool with relevant SQL. The LLM may also choose to collapse details of the tool call, especially when it decides to weave the results within its actual response. That said, full details can always be inspected by clicking on the &ldquo;Query Data&rdquo; tool call display:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-query.png" alt="Query tool in action, showing the SQL query and the resulting summary in the chat response" class="shadow rounded" />
<h3 id="visualize-tool">Visualize tool
</h3>
<p>For questions that are better suited to a visual response, the LLM requests the <code>visualize</code> tool with relevant ggsql. In addition to rendering the plot inline for the user to see, that same plot is also provided to the LLM so that it can interpret the result:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-viz.png" alt="Visualize tool in action, showing the ggsql query and the resulting chart in the chat response" class="shadow rounded" />
<p>A couple noteworthy things about this experience:</p>
<ul>
<li>The footer includes options to view the underlying ggsql query, download the plot as an image, and expand the display to full screen.</li>
<li>Plots are always shown by default, but can be collapsed by clicking the header.</li>
</ul>
<p>This hopefully gives you a taste for how easy it is to get set up with a basic <code>querychat</code> UI and start asking questions of your data.
You may, however, be wondering how <code>querychat</code> is able to produce correct queries and how the code execution actually works. Let&rsquo;s dive into those details next.</p>
<h2 id="how-does-it-work">How does it work?
</h2>
<h3 id="execution">Execution
</h3>
<p>One important thing to understand about <code>querychat</code> is that the LLM itself is not handling the SQL execution &ndash; <code>querychat</code> is. Where that execution ultimately happens depends on what data source you&rsquo;re providing. If you&rsquo;re pointing <code>querychat</code> at an in-memory data frame, the execution happens in-process using DuckDB. If you&rsquo;re pointing it at a database connection, the execution happens against that database. In either case, the LLM is generating queries based on its understanding of the data schema and any additional context you&rsquo;ve provided, not the actual data. This separation is what allows <code>querychat</code> to maintain a strong security posture, scale to large data, and deliver a good user experience.</p>
<h3 id="schema-discovery">Schema discovery
</h3>
<p>To elaborate on what &ldquo;understanding of the data schema&rdquo; actually means: when you point <code>querychat</code> at a data source, it automatically extracts column names, types, numerical ranges, and categorical values. This information is included in the system prompt for the LLM, so it has a clear picture of what the data looks like and can generate accurate queries without needing to see the actual data. For many datasets, this is enough to get good results out of the box.</p>
<h3 id="additional-context">Additional context
</h3>
<p>For more complex datasets or domain-specific questions, you can also provide additional context through a plain-text data description. <code>querychat</code> now also automatically picks up on <a href="https://www.snowflake.com/en/developers/guides/snowflake-semantic-view/" target="_blank" rel="noopener">Snowflake Semantic Models</a> when connected to a Snowflake database, giving the LLM access to authoritative definitions of business logic and metrics without any manual configuration. We hope to add more integrations to other semantic layer formats in the future.</p>
<p>To learn more, <code>querychat</code>&rsquo;s website has more details on <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">data sources</a> (<a href="https://posit-dev.github.io/querychat/r/articles/data-sources.html" target="_blank" rel="noopener">R</a>), <a href="https://posit-dev.github.io/querychat/py/context.html" target="_blank" rel="noopener">providing context</a> (<a href="https://posit-dev.github.io/querychat/r/articles/context.html" target="_blank" rel="noopener">R</a>), and <a href="https://posit-dev.github.io/querychat/py/tools.html" target="_blank" rel="noopener">tool execution</a> (<a href="https://posit-dev.github.io/querychat/r/articles/tools.html" target="_blank" rel="noopener">R</a>).</p>
<h2 id="chat-driven-dashboards">Chat-driven dashboards
</h2>
<p>A chat interface alone likely isn&rsquo;t the experience you want to ship to end users &mdash; the better pattern is to surface interesting findings first, then let users explore beyond them. This is where <code>querychat</code> begins to really shine: the chat interface we already covered can easily be embedded inside of a larger app that includes other outputs &ndash; plots, tables, value boxes, etc. <code>querychat</code> comes with another tool designed for this use case &ndash; the filter tool &ndash; allowing the LLM to effectively drill down into relevant sections of the data in the dashboard in response to user questions.</p>
<p>The key integration point is <code>df()</code>, a reactive value that reflects the current state of the data after any filtering applied by the LLM. Use <code>df()</code> as the data source for your plots, tables, and value boxes, and they&rsquo;ll automatically update in response to user questions in the chat.</p>
<style>
style + .panel-tabset .highlight .chroma {
  max-height: 500px;
  overflow-y: auto !important;
}
</style>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">Python</a></li>
<li><a href="#tabset-3-2">R</a></li>
</ul>
<div id="tabset-3-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-6">
  <div class="code-with-filename-label" id="code-filename-6"><span class="font-mono text-sm">app.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">,</span> <span class="s2">&#34;query&#34;</span><span class="p">,</span> <span class="s2">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">sidebar</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;binoculars&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Penguins&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">count</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;fingerprint&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Species&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">species</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()[</span><span class="s2">&#34;species&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;gauge-high&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Avg Body Mass&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">avg_mass</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()[</span><span class="s1">&#39;body_mass_g&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">g&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Bill Dimensions&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.plot</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">scatter</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="kn">import</span> <span class="nn">plotnine</span> <span class="k">as</span> <span class="nn">p9</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">p9</span><span class="o">.</span><span class="n">ggplot</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">(),</span> <span class="n">p9</span><span class="o">.</span><span class="n">aes</span><span class="p">(</span><span class="s2">&#34;bill_length_mm&#34;</span><span class="p">,</span> <span class="s2">&#34;bill_depth_mm&#34;</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;species&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="o">+</span> <span class="n">p9</span><span class="o">.</span><span class="n">geom_point</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Measurements&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-3-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-7">
  <div class="code-with-filename-label" id="code-filename-7"><span class="font-mono text-sm">app.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">DT</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">palmerpenguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tools</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">,</span> <span class="s">&#34;query&#34;</span><span class="p">,</span> <span class="s">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidebar</span> <span class="o">=</span> <span class="n">qc</span><span class="o">$</span><span class="nf">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Penguins&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;count&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;binoculars&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Species&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;species&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;fingerprint&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Avg Body Mass&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;avg_mass&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;speedometer&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span><span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Bill Dimensions&#34;</span><span class="p">),</span> <span class="nf">plotOutput</span><span class="p">(</span><span class="s">&#34;scatter&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span><span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Measurements&#34;</span><span class="p">),</span> <span class="nf">DTOutput</span><span class="p">(</span><span class="s">&#34;table&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">qc_vals</span> <span class="o">&lt;-</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="n">df</span> <span class="o">&lt;-</span> <span class="n">qc_vals</span><span class="o">$</span><span class="n">df</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">count</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">nrow</span><span class="p">(</span><span class="nf">df</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">species</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="nf">unique</span><span class="p">(</span><span class="nf">df</span><span class="p">()</span><span class="o">$</span><span class="n">species</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">avg_mass</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">paste0</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">mean</span><span class="p">(</span><span class="nf">df</span><span class="p">()</span><span class="o">$</span><span class="n">body_mass_g</span><span class="p">,</span> <span class="n">na.rm</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)),</span> <span class="s">&#34;g&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">scatter</span> <span class="o">&lt;-</span> <span class="nf">renderPlot</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">ggplot</span><span class="p">(</span><span class="nf">df</span><span class="p">(),</span> <span class="nf">aes</span><span class="p">(</span><span class="n">bill_length_mm</span><span class="p">,</span> <span class="n">bill_depth_mm</span><span class="p">,</span> <span class="n">color</span> <span class="o">=</span> <span class="n">species</span><span class="p">))</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">geom_point</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">table</span> <span class="o">&lt;-</span> <span class="nf">renderDT</span><span class="p">(</span><span class="nf">df</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/dashboard.png" alt="QueryChat embedded in a dashboard with value boxes, a scatter plot, and a data table — all reactively driven by the chat's filter state" class="shadow rounded" />
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Python users: Shiny v Streamlit/Dash/Gradio</span>
</div>
<div class="callout-body">
<p><code>querychat</code> also supports <code>streamlit</code>, <code>dash</code>, and <code>gradio</code>. That said, the reactive <code>df()</code> pattern shown above &mdash; where the LLM&rsquo;s filter state automatically drives every view in the app &mdash; is what Shiny&rsquo;s reactive programming model was designed for. In other frameworks, keeping the chat&rsquo;s state in sync with multiple outputs typically requires more manual wiring.</p>
</div>
</div>
<h2 id="custom-tools">Custom tools
</h2>
<p><code>querychat</code> comes with three built-in tools, but you can also easily add your own custom tools. This is possible thanks to the extensible foundation provided by <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener">chatlas</a> (<a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>). These packages also make it quite easy to implement tools &ndash; all you really need is a Python / R function that performs some operation. You can also fully customize the display shown, thanks to their rich support for <a href="https://posit-dev.github.io/chatlas/tool-calling/displays.html" target="_blank" rel="noopener">tool call displays</a> (<a href="https://posit-dev.github.io/shinychat/r/articles/tool-ui.html" target="_blank" rel="noopener">R</a>).</p>
<p>To give you a sense of what other capabilities are possible, you could start out as simple as querying <a href="https://posit-dev.github.io/chatlas/get-started/tools.html" target="_blank" rel="noopener">real-time weather information</a>, but also get as sophisticated as a <a href="https://posit-dev.github.io/chatlas/misc/RAG.html#dynamic-retrieval" target="_blank" rel="noopener">RAG-like knowledge retrieval agent</a>. For example, here&rsquo;s how you&rsquo;d let users ask whether weather conditions might relate to trends in your data:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">Python</a></li>
<li><a href="#tabset-4-2">R</a></li>
</ul>
<div id="tabset-4-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatLMStudio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">=</span> <span class="n">ChatLMStudio</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_current_weather</span><span class="p">(</span><span class="n">lat</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">lng</span><span class="p">:</span> <span class="nb">float</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the current weather for a location.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat_lng</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;latitude=</span><span class="si">{</span><span class="n">lat</span><span class="si">}</span><span class="s2">&amp;longitude=</span><span class="si">{</span><span class="n">lng</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;https://api.open-meteo.com/v1/forecast?</span><span class="si">{</span><span class="n">lat_lng</span><span class="si">}</span><span class="s2">&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;current&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">get_current_weather</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="n">chat_client</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-4-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">&lt;-</span> <span class="nf">chat_lmstudio</span><span class="p">(</span><span class="n">model</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">get_current_weather</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lng</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">lat_lng</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;latitude=&#34;</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="s">&#34;&amp;longitude=&#34;</span><span class="p">,</span> <span class="n">lng</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;https://api.open-meteo.com/v1/forecast?&#34;</span><span class="p">,</span> <span class="n">lat_lng</span><span class="p">,</span> <span class="s">&#34;&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">response</span> <span class="o">&lt;-</span> <span class="n">httr</span><span class="o">::</span><span class="nf">GET</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">httr</span><span class="o">::</span><span class="nf">content</span><span class="p">(</span><span class="n">response</span><span class="p">)</span><span class="o">$</span><span class="n">current</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">get_current_weather</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Get the current weather for a location&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lng</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">palmerpenguins</span><span class="o">::</span><span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="n">chat_client</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Underneath the hood, <code>querychat</code>&rsquo;s built-in tools use this same foundation. The &ldquo;magic&rdquo; of these tools really breaks down to three key insights:</p>
<ol>
<li>LLMs are very good at translating natural language into code.</li>
<li>Tool call arguments can be executable code (like SQL or ggsql) rather than just simple values.</li>
<li>Tool calls can <a href="https://shiny.posit.co/py/docs/genai-tools.html#managing-state" target="_blank" rel="noopener">manage reactive state in a Shiny app</a> &mdash; for <code>querychat</code>, that state is the current filter query, but the same pattern could control input controls, tab selection, or other UI state.</li>
</ol>
<h2 id="whats-next">What&rsquo;s next?
</h2>
<p>This release is just the beginning of our vision for (responsibly) exploring data via natural language. We&rsquo;re excited to see what you build with it, and we&rsquo;re already thinking about the next set of features to add. Some things on our near-term roadmap:</p>
<ul>
<li>Multiple tables.</li>
<li>Reproducible takeaway artifacts, like <a href="https://quarto.org/docs/dashboards/" target="_blank" rel="noopener">Quarto dashboards</a></li>
<li>Support for more semantic layer solutions beyond Snowflake, so more users can give the LLM authoritative definitions of their business logic.</li>
</ul>
<h2 id="learn-more">Learn more
</h2>
<ul>
<li><a href="https://posit-dev.github.io/querychat/py/" target="_blank" rel="noopener">querychat documentation</a> (<a href="https://posit-dev.github.io/querychat/r/" target="_blank" rel="noopener">R</a>) &mdash; full guides on data sources, context, tools, and deployment</li>
<li><a href="https://ggsql.org" target="_blank" rel="noopener">ggsql</a> &mdash; the grammar of graphics for SQL that powers querychat&rsquo;s visualizations</li>
<li><a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener">chatlas</a> (<a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>) &mdash; the underlying LLM tool-calling libraries, useful for building custom tools</li>
<li><a href="https://posit-dev.github.io/shinychat/py/" target="_blank" rel="noopener">shinychat</a> (<a href="https://posit-dev.github.io/shinychat/r/" target="_blank" rel="noopener">R</a>) &mdash; the chat UI component that querychat builds on</li>
<li><a href="https://shiny.posit.co/py/" target="_blank" rel="noopener">Shiny</a> (<a href="https://shiny.posit.co/r/" target="_blank" rel="noopener">R</a>) &mdash; the web framework powering querychat apps</li>
<li><a href="https://github.com/posit-dev/querychat" target="_blank" rel="noopener">Source on GitHub</a> &mdash; issues, discussions, and contributions welcome</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gist.github.com/cpsievert/ca0d7e4637fdf994671c9d9fc90cd89f" target="_blank" rel="noopener">Source code</a> is available for reference, though you&rsquo;ll need your own Snowflake account and credentials to run it.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/featured.png" length="454060" type="image/png" />
    </item>
    <item>
      <title>Make your SciPy presentation in Quarto</title>
      <link>https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/</link>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/</guid>
      <dc:creator>Isabella Velásquez</dc:creator>
      <dc:creator>Charlotte Wickham</dc:creator><description><![CDATA[<p><a href="https://www.scipy2026.scipy.org/" target="_blank" rel="noopener">SciPy 2026</a> is soon, and we are psyched! We have a <a href="https://opensource.posit.co/events/scipy-2026/" target="_blank" rel="noopener">whole troupe of Posit folks</a> heading to Minneapolis to celebrate open source, scientific computing, and Python tooling.</p>
<p>If you are preparing for your next talk (whether at SciPy or another conference), you might be looking for the most effective way to showcase code and its output in your deck. And, if we do say so ourselves, <a href="https://quarto.org/" target="_blank" rel="noopener">Quarto</a> is the premier tool for creating polished presentations that shine in scientific contexts.</p>
<p>Why? Because Quarto was designed just for this purpose! As an open-source scientific and technical publishing system, Quarto allows you to craft dynamic content that weaves your narrative together with Markdown and Python code, and then renders them into a single file that elegantly showcases your results. By using a single source file, you don&rsquo;t have to manually copy and paste code or its outputs. Any changes you make are reflected in the latest version by rerendering the document.</p>
<p>Hopefully, this has piqued your interest!
Curious how it looks? We built the deck below in Quarto; keep reading to learn how.</p>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-08.html" loading="lazy" width="560" height="373">
</iframe>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Navigating slides</span>
</div>
<div class="callout-body">
<p>To advance through the slides, hit <kbd>Space</kbd> or <kbd>→</kbd>.
To go back, hit <kbd>←</kbd>.
Click the hamburger menu (<span class="icon-[codicon--menu] align-text-bottom"></span>) in the slide deck to see other options.</p>
</div>
</div>
<h2 id="get-started-with-slides-in-quarto">Get started with slides in Quarto
</h2>
<h3 id="install-quarto">Install Quarto
</h3>
<p>We recommend using <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a> as your editor, since Quarto is preinstalled. If you are using VS Code, install the <a href="https://quarto.org/docs/download/" target="_blank" rel="noopener">Quarto CLI</a> and the <a href="https://marketplace.visualstudio.com/items?itemName=quarto.quarto" target="_blank" rel="noopener">Quarto VS Code Extension</a>.</p>
<h3 id="create-a-new-document">Create a new document
</h3>
<p>Open a new folder, enter the Command Palette (<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>) and search for <code>Quarto: New Quarto document (qmd)</code>. Save the file as <code>index.qmd</code>.</p>
<h3 id="change-the-format-to-revealjs">Change the format to revealjs
</h3>
<p>Your new document will include a YAML header for managing your document&rsquo;s metadata. You&rsquo;ll see that the default is set to HTML via <code>format: html</code>:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-0">
  <div class="code-with-filename-label" id="code-filename-0"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Untitled&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>However, Quarto is incredibly versatile, and it can render your work into PDFs, Word documents, and much more! We recommend the reveal.js format for creating aesthetically pleasing code-centric slides. reveal.js also provides a variety of useful features tailored for displaying scientific results and technical workflows.</p>
<p>You can switch your document from HTML to reveal.js by updating the format in your YAML header to <code>format: revealjs</code>:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-1">
  <div class="code-with-filename-label" id="code-filename-1"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Untitled&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">revealjs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>Click the Preview button (<span class="icon-[codicon--preview] align-text-bottom"></span>) at the top left of your editor to see what your presentation looks like!</p>
<p>You can also preview from the command line:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-2">
  <div class="code-with-filename-label" id="code-filename-2"><span class="font-mono text-sm">Terminal</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">quarto preview index.qmd</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-01.html" loading="lazy" width="560" height="373">
</iframe>
<h2 id="build-the-slides">Build the slides
</h2>
<p>From here, we can move on to the fun part: building our slides!</p>
<h3 id="edit-metadata">Edit metadata
</h3>
<p>As alluded to earlier, we edit the metadata document by modifying the YAML header. You can change the title, add yourself as an author, and add your presentation date:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-3">
  <div class="code-with-filename-label" id="code-filename-3"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Make your SciPy presentation in Quarto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">author</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">Isabella Velásquez</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">Charlotte Wickham</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2026-07-14</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">revealjs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>Those values then set up your title slide.</p>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-02.html" loading="lazy" width="560" height="373">
</iframe>
<h3 id="add-your-first-slide">Add your first slide
</h3>
<p>Below the YAML header is where you write your slides. Quarto uses Markdown, a text formatting language, for text:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-4">
  <div class="code-with-filename-label" id="code-filename-4"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;Make your SciPy presentation in Quarto&#34;
</span></span><span class="line"><span class="cl">author:
</span></span><span class="line"><span class="cl">  <span class="k">-</span> Isabella Velásquez
</span></span><span class="line"><span class="cl">  <span class="k">-</span> Charlotte Wickham
</span></span><span class="line"><span class="cl">date: 2026-07-14
</span></span><span class="line"><span class="cl">format: revealjs
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Slides are just Markdown and code:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Write your narrative in <span class="gs">**Markdown**</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> Weave in <span class="gs">**Python**</span> code and its output
</span></span><span class="line"><span class="cl">- Render it all to one reproducible file</span></span></code></pre></div></div>
<p>You can control aspects of your presentation by nesting options under <code>revealjs</code> in the YAML header.
For example, you can make any list display incrementally by adding <code>incremental: true</code>:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-5">
  <div class="code-with-filename-label" id="code-filename-5"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Make your SciPy presentation in Quarto&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">author</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">Isabella Velásquez</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">Charlotte Wickham</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2026-07-14</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">format</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">revealjs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">incremental</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>Now as you advance the slides, the bullets appear one at a time.</p>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-03.html#/1" loading="lazy" width="560" height="373">
</iframe>
<h3 id="add-a-new-slide">Add a new slide
</h3>
<p>To add a new slide, add a new heading. A Level 1 heading adds a new section, while a Level 2 heading adds a new slide.</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-6">
  <div class="code-with-filename-label" id="code-filename-6"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gh"># Features for showing code
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">## Add Python code cells</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-04.html#/features-for-showing-code" loading="lazy" width="560" height="373">
</iframe>
<p>Continue adding new slides until you&rsquo;re done!</p>
<h3 id="add-python-code-cells">Add Python code cells
</h3>
<p>To add Python code to your slide, put it within a code cell, which is delineated by three backticks <code>```</code> and <code>python</code> within curly braces. By default, Quarto reveal.js presentations do not show your code cells, because they assume you are presenting just your output. If you do want to show your code, you can add a <em>cell option</em>, designated by a special comment, <code>#|</code>, followed by the option using YAML syntax. In this case, <code>echo: true</code> lets Quarto know to show the code.</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-7">
  <div class="code-with-filename-label" id="code-filename-7"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Add Python code cells
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">``<span class="sb">`{python}
</span></span></span><span class="line"><span class="cl"><span class="sb">#| echo: true
</span></span></span><span class="line"><span class="cl"><span class="sb">from plotnine import *
</span></span></span><span class="line"><span class="cl"><span class="sb">from plotnine.data import anscombe_quartet
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-04b.html#/add-python-code-cells" loading="lazy" width="560" height="373">
</iframe>
<h3 id="code-output-is-included">Code output is included
</h3>
<p>When you render, Quarto runs the code cells and includes their output right below the code:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-8">
  <div class="code-with-filename-label" id="code-filename-8"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Code output is included
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">``<span class="sb">`{python}
</span></span></span><span class="line"><span class="cl"><span class="sb">#| echo: true
</span></span></span><span class="line"><span class="cl"><span class="sb">anscombe_quartet.head()
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-04c.html#/code-output-is-included" loading="lazy" width="560" height="373">
</iframe>
<h3 id="show-code-without-running-it">Show code without running it
</h3>
<p>Some code only makes sense to run live &mdash; like <code>%view</code>, which opens your data in Positron&rsquo;s Data Explorer. To show such code without running it, add the <code>eval: false</code> cell option, which tells Quarto to show the cell but not evaluate it:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-9">
  <div class="code-with-filename-label" id="code-filename-9"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## You can show code without running it
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">``<span class="sb">`{python}
</span></span></span><span class="line"><span class="cl"><span class="sb">#| echo: true
</span></span></span><span class="line"><span class="cl"><span class="sb">#| eval: false
</span></span></span><span class="line"><span class="cl"><span class="sb">%view anscombe_quartet
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-05.html#/you-can-show-code-without-running-it" loading="lazy" width="560" height="373">
</iframe>
<h3 id="add-a-theme">Add a theme
</h3>
<p>The Quarto default look is great, but say you want to add your own theming. You can do that in a single YAML file using <a href="https://posit-dev.github.io/brand-yml/" target="_blank" rel="noopener">brand.yml</a>.</p>
<p>In your root directory, create a file called <code>_brand.yml</code>. Add specifications as listed on the <a href="https://posit-dev.github.io/brand-yml/brand/" target="_blank" rel="noopener">brand.yml documentation site</a>. You can edit the font, colors, and more (add as much or as little as you&rsquo;d like). Next time you render your slides, the brand will be automatically picked up!</p>
<p>If you&rsquo;d like a <code>_brand.yml</code> file to get you started, <a href="https://gist.github.com/ivelasq/fa6e52022ee9e390c0b12d4242155fa5" target="_blank" rel="noopener">here is one that mimics the SciPy conference style</a>. Put it in the root directory of your presentation, and it will look like this:</p>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-brand.html" loading="lazy" width="560" height="373">
</iframe>
<h2 id="features-for-showcasing-python-code">Features for showcasing Python code
</h2>
<p>You may be thinking, this is great, but I can make slides in any old software! True, but Quarto has specific features that really elevate your audience&rsquo;s experience.</p>
<h3 id="code-line-highlighting">Code line highlighting
</h3>
<p>Sure, you can show a block of code on your slide. But you&rsquo;re usually talking about a specific line or lines of code at a time. If you want to draw your audience&rsquo;s attention to a specific section of your code, you can use <a href="https://quarto.org/docs/presentations/revealjs/index.html#line-highlighting" target="_blank" rel="noopener">code line highlighting</a>.</p>
<p>For example, if you want to first highlight line 2, then lines 3 through 4, then line 5, you can add the <code>code-line-numbers</code> cell option:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-10">
  <div class="code-with-filename-label" id="code-filename-10"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">``<span class="sb">`{python}
</span></span></span><span class="line"><span class="cl"><span class="sb">#| echo: true
</span></span></span><span class="line"><span class="cl"><span class="sb">#| code-line-numbers: &#34;2|3-4|5&#34;
</span></span></span><span class="line"><span class="cl"><span class="sb">(
</span></span></span><span class="line"><span class="cl"><span class="sb">    anscombe_quartet
</span></span></span><span class="line"><span class="cl"><span class="sb">    .groupby(&#34;dataset&#34;)[[&#34;x&#34;, &#34;y&#34;]]
</span></span></span><span class="line"><span class="cl"><span class="sb">    .apply(lambda g: g[&#34;x&#34;].corr(g[&#34;y&#34;]))
</span></span></span><span class="line"><span class="cl"><span class="sb">    .round(2).to_frame(&#34;correlation&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">)
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<p>Scroll through to see what it looks like:</p>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-06.html#/highlight-lines-as-you-talk" loading="lazy" width="560" height="373">
</iframe>
<h3 id="show-code-output-in-a-new-slide">Show code output in a new slide
</h3>
<p>When rendered, the slides will automatically resize based on the content. However, this means if you have a lot of code and a large output (like an image), it will be hard to see both in a single slide.</p>
<p>Luckily, the Quarto developers have thought of this and have the <code>output-location</code> cell option where you designate where the output should show up. For example, to show the output on the new slide, set <code>output-location</code> to <code>slide</code>:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-11">
  <div class="code-with-filename-label" id="code-filename-11"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">``<span class="sb">`{python}
</span></span></span><span class="line"><span class="cl"><span class="sb">#| echo: true
</span></span></span><span class="line"><span class="cl"><span class="sb">#| output-location: slide
</span></span></span><span class="line"><span class="cl"><span class="sb">(
</span></span></span><span class="line"><span class="cl"><span class="sb">    ggplot(anscombe_quartet, aes(&#34;x&#34;, &#34;y&#34;))
</span></span></span><span class="line"><span class="cl"><span class="sb">    + geom_point(color=&#34;sienna&#34;, fill=&#34;orange&#34;, size=3)
</span></span></span><span class="line"><span class="cl"><span class="sb">    + geom_smooth(method=&#34;lm&#34;, se=False, fullrange=True,
</span></span></span><span class="line"><span class="cl"><span class="sb">                  color=&#34;steelblue&#34;, size=1)
</span></span></span><span class="line"><span class="cl"><span class="sb">    + facet_wrap(&#34;dataset&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">    + labs(title=&#34;Anscombe’s Quartet&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">    + scale_y_continuous(breaks=(4, 8, 12))
</span></span></span><span class="line"><span class="cl"><span class="sb">    + coord_fixed(xlim=(3, 22), ylim=(2, 14))
</span></span></span><span class="line"><span class="cl"><span class="sb">    + theme_tufte(base_family=&#34;Futura&#34;, base_size=16)
</span></span></span><span class="line"><span class="cl"><span class="sb">    + theme(
</span></span></span><span class="line"><span class="cl"><span class="sb">        axis_line=element_line(color=&#34;#4d4d4d&#34;),
</span></span></span><span class="line"><span class="cl"><span class="sb">        axis_ticks_major=element_line(color=&#34;#00000000&#34;),
</span></span></span><span class="line"><span class="cl"><span class="sb">        axis_title=element_blank(),
</span></span></span><span class="line"><span class="cl"><span class="sb">        panel_spacing=0.09,
</span></span></span><span class="line"><span class="cl"><span class="sb">    )
</span></span></span><span class="line"><span class="cl"><span class="sb">)
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-07.html#/send-big-output-to-its-own-slide" loading="lazy" width="560" height="373">
</iframe>
<h3 id="add-a-filename-and-extension">Add a filename and extension
</h3>
<p>A subtle (but very helpful) feature of Quarto is the ability to specify the filename and extension of a code block, which you can use if you are talking about different files or programming languages and want to visually distinguish them. Add <code>filename=</code> to your code block:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-12">
  <div class="code-with-filename-label" id="code-filename-12"><span class="font-mono text-sm">index.qmd</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">``<span class="sb">`{.bash filename=&#34;Terminal&#34;}
</span></span></span><span class="line"><span class="cl"><span class="sb">pip install pandas
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>`<span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">&lt;br&gt;
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>`<span class="sb">`{.python filename=&#34;script.py&#34;}
</span></span></span><span class="line"><span class="cl"><span class="sb">import pandas as pd
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<iframe class="slide-deck" src="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/slide-08.html#/label-code-with-filenames" loading="lazy" width="560" height="373">
</iframe>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Code blocks vs. code cells</span>
</div>
<div class="callout-body">
<p>The <code>.</code> in front of the language in the code above (e.g. <code>.python</code>) signals these are <em>code blocks</em>, not <em>executable code cells</em>.
Quarto doesn&rsquo;t attempt to execute code blocks, but you get nice syntax highlighting based on the language specified.</p>
</div>
</div>
<h2 id="extensions-for-scientific-presentation">Extensions for scientific presentation
</h2>
<p>So far, we&rsquo;ve just mentioned features that are built into Quarto; however, one of the most powerful features of Quarto is its <a href="https://quarto.org/docs/extensions/" target="_blank" rel="noopener">extension system</a>. Both the Posit developers and the community have built an incredible ecosystem of extensions that augment what Quarto can do. There are hundreds of extensions (see the <a href="https://m.canouil.dev/quarto-extensions/" target="_blank" rel="noopener">Quarto gallery</a> by Mickaël Canouil), but here are a curated few.</p>
<h3 id="quartolive-extension-for-interactive-python-code-blocks">QuartoLive extension for interactive Python code blocks
</h3>
<p>You no longer have to switch between your slides and your IDE to run code! Using <a href="https://r-wasm.github.io/quarto-live/" target="_blank" rel="noopener">QuartoLive</a>, you can embed interactive Python code blocks. Run the code, edit it, and rerun, all within your Quarto reveal.js presentation.</p>
<h3 id="provide-a-direct-link-to-your-slides">Provide a direct link to your slides
</h3>
<p>Usually, the first question asked during a presentation is, &ldquo;Will the slides be provided?&rdquo; Evade that question by using the <a href="https://github.com/jmbuhr/quarto-qrcode" target="_blank" rel="noopener">QR code extension</a>, which adds a QR code that the audience can easily scan while they watch so they can find the slides after the presentation is over.</p>
<h3 id="add-accessibility-features">Add accessibility features
</h3>
<p>For your audience to be able to read the text on your slides, the font size should <a href="https://thinkoutsidetheslide.com/wp-content/uploads/2012/08/ViewingDistanceTable16x9.pdf" target="_blank" rel="noopener">probably be larger than you anticipate</a>.</p>
<p>The <a href="https://github.com/mcanouil/quarto-revealjs-a11y" target="_blank" rel="noopener">reveal.js A11y</a> extension provides accessibility features for your slide deck. One nifty feature is the ability to increase the font size on the fly. Enter the menu by pressing <kbd>A</kbd>, and then zoom in to what you need.</p>
<h2 id="publish-and-share-your-slides">Publish and share your slides
</h2>
<p>After you&rsquo;ve done the fun work of creating your slides, you&rsquo;ll want to publish and share them with others. We recommend <a href="https://connect.posit.cloud/" target="_blank" rel="noopener">Posit Connect Cloud</a>, which has a generous free tier for Quarto documents, Streamlit and Shiny apps, and much more.</p>
<p>If you&rsquo;re working in Positron, the <a href="https://docs.posit.co/connect-cloud/user/publish/ide.html" target="_blank" rel="noopener">Posit Publisher extension</a> is built in and gives you push-button deployment to Posit Connect Cloud right from your editor (in VS Code, grab it from the <a href="https://marketplace.visualstudio.com/items?itemName=Posit.publisher" target="_blank" rel="noopener">Marketplace</a>).</p>
<p>If not, you can also publish from the command line with <code>quarto publish</code>, which renders your slides and uploads them:</p>
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-13">
  <div class="code-with-filename-label" id="code-filename-13"><span class="font-mono text-sm">Terminal</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">quarto publish posit-connect-cloud index.qmd</span></span></code></pre></div></div>
<p>The slides for this very post are published at <a href="https://posit-devrel-make-your-scipy-presentation-in-quarto.share.connect.posit.cloud" target="_blank" rel="noopener">posit-devrel-make-your-scipy-presentation-in-quarto.share.connect.posit.cloud</a>.</p>
<p>Press <kbd>F</kbd> to enter full screen, and you&rsquo;re ready to present!</p>
<h2 id="welcome-to-the-wonderful-world-of-quarto-slidecrafting">Welcome to the wonderful world of Quarto slidecrafting
</h2>
<p>We hope that this post inspired you to give Quarto presentations a try!</p>
<p>We&rsquo;ve only scratched the surface &mdash; the <a href="https://quarto.org/docs/presentations/revealjs/" target="_blank" rel="noopener">Quarto reveal.js documentation</a> covers the full set of features for building presentations.</p>
<p>If you are completely new to Quarto, <a href="https://www.youtube.com/watch?v=QU0N--y-skA&amp;lc=Ugxz1YOUWPrHjQAq0oZ4AaABAg" target="_blank" rel="noopener">Charlotte&rsquo;s YouTube video</a> is a great introduction to setting it up, creating a document from scratch, and rendering it.</p>
<p>If you want to see what else is possible, Emil Hvitfeldt is working on a book called <a href="https://slidecrafting-book.com/" target="_blank" rel="noopener">Slidecrafting</a> on creating functional and beautiful Quarto reveal.js presentations through theming, layout, interactivity, and extensions.</p>
<p>And definitely look through Mickaël&rsquo;s <a href="https://m.canouil.dev/quarto-extensions/" target="_blank" rel="noopener">Quarto extension gallery</a> to see if other extensions are useful to you. His blog post, <a href="https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/" target="_blank" rel="noopener">Quarto Reveal.js Extensions to Sharpen Your Slides</a>, also has more advanced tricks you can apply to your scientific and technical publications.</p>
<p>Do let us know what you end up creating!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-15_build-your-scipy-slides-with-quarto/featured.jpg" length="132652" type="image/jpeg" />
    </item>
    <item>
      <title>A brief and biased history of Posit data science agents</title>
      <link>https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/</link>
      <pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/</guid>
      <dc:creator>Joe Cheng</dc:creator><description><![CDATA[<p>For the last 18 months, many of us at Posit have been focused on building AI agents designed to help RStudio and Positron users get their work done faster and more effectively. And we truly believe that we&rsquo;ve succeeded! Our latest agent, <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, is extremely good and getting better with each passing week.</p>
<p>But Posit Assistant was not our first agent, and it&rsquo;s not likely to be our last. The proliferation of agents coming from Posit has started causing serious confusion. This post hopes to clear that up by providing a chronology of the agents we&rsquo;ve created, our rationale for their design and architecture, and what you need to know about them today (if anything).</p>
<h2 id="tldr">TL;DR
</h2>
<p>Posit Assistant is our newest, most powerful agent for data science. It builds on what we learned from creating our previous agents: Positron Assistant and Databot.</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Description</th>
          <th>Status</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong><a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a></strong></td>
          <td>Coding agent available in RStudio and Positron (and more)</td>
          <td>Active</td>
      </tr>
      <tr>
          <td><strong><a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Positron Assistant</a></strong></td>
          <td>Positron&rsquo;s coding agent</td>
          <td>Superseded by Posit Assistant, still available until Q3 2026</td>
      </tr>
      <tr>
          <td><strong><a href="https://positron.posit.co/databot.html" target="_blank" rel="noopener">Databot</a></strong></td>
          <td>EDA agent in Positron</td>
          <td>Superseded by Posit Assistant</td>
      </tr>
  </tbody>
</table>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">What about Posit AI?</span>
</div>
<div class="callout-body">
<p><a href="https://posit.ai/" target="_blank" rel="noopener">Posit AI</a>, not to be confused with Posit Assistant or Positron Assistant, is a model subscription service that can be used to power Posit Assistant.</p>
</div>
</div>
<h2 id="positron-assistant">Positron Assistant
</h2>
<ul>
<li><strong>Strengths:</strong> Time to market; connects to a variety of providers; tight IDE integration</li>
<li><strong>Weaknesses:</strong> Legacy agent architecture; not available in RStudio IDE</li>
<li><strong>Status:</strong> Succeeded by Posit Assistant (as of June 2026)</li>
</ul>
<p>This was the first &ldquo;off the shelf&rdquo; data science agent we ever shipped, and is available exclusively in Positron. It was forked from GitHub Copilot, and like Copilot, you can ask it general coding questions, have it write code, and have it explain existing code to you.</p>
<p>While the <a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Positron Assistant</a> experience is largely the same as Copilot, there are two major differences. The first is that it can access Positron-specific features like running code in an interactive R or Python session, inspecting variables, and generating/inspecting plots. These features make it much more useful for data work.</p>
<p>The second major difference is the ability to connect to other LLM providers than the Copilot service, right out of the box. At this time, Positron Assistant can connect to Anthropic and OpenAI (with API key), AWS Bedrock, Snowflake Cortex, GitHub Copilot, Microsoft Foundry, Google Gemini, <a href="https://posit.ai/" target="_blank" rel="noopener">Posit AI</a>, and even custom (OpenAI-compatible) providers. When running in a properly configured Posit Workbench installation, these can often be seamlessly authenticated.</p>
<p>GitHub Copilot is tightly integrated with VS Code, and Positron Assistant is similarly well integrated with Positron. You can start an &ldquo;inline chat&rdquo; right at your editor cursor and ask Positron Assistant to fix or explain errors with one click. These were major benefits to starting with GitHub Copilot as a base.</p>
<p>Unfortunately, there are serious downsides to the GitHub Copilot heritage as well. The architecture and core APIs of Copilot were designed to be extended <a href="https://code.visualstudio.com/api/extension-guides/ai/chat" target="_blank" rel="noopener">in ways</a> that didn&rsquo;t end up being very popular, while tool calling felt grafted on and cache control nonexistent. The net effect was a lot of abstraction and complexity between the user prompt and what actually went into the LLM request, making it difficult for us to get the best performance out of the LLM. And users noticed this, saying that Positron Assistant was generally effective but also duller than other agents, including Databot and Claude Code, even when using the same underlying model.</p>
<p>Another big downside to GitHub Copilot was that its chat UI was more austere and harder to customize than what we could do on our own. Any changes we made were asking for technical debt, multiplied by the incredibly fast pace of upstream changes that we need to regularly merge.</p>
<p>Overall, the Positron Assistant project gave us what we needed at the time: a competitive, full-featured, IDE-integrated, agentic coding assistant. But for data work, we felt like we knew how to do better in a number of ways, including agent performance and UI/UX.</p>
<h2 id="databot">Databot
</h2>
<ul>
<li><strong>Strengths:</strong> Effective agent harness; well tailored to exploration tasks</li>
<li><strong>Weaknesses:</strong> Not general purpose enough; not available in RStudio IDE</li>
<li><strong>Status:</strong> Succeeded by Posit Assistant (as of June 2026)</li>
</ul>
<p>Databot was conceived in December 2024, in response to a specific question: what would happen if we took a frontier model (Claude 3.5 Sonnet), seeded it with simple instructions, and gave it unfettered access to the most powerful tool we could think of (a live R or Python session)?</p>
<p>At the time, it seemed hopelessly reckless to give so much power to an LLM, and we ran the first prototype with real trepidation! Fortunately, this approach is more effective and significantly less dangerous in practice than in theory. In a world before Claude Code, Databot felt like absolute magic. That magic was compounded by three decisions we made in an effort to make Databot excellent for exploratory data analysis (EDA):</p>
<ol>
<li>The ability not only to execute code but to see the results (including tables and plots)</li>
<li>Keeping the human in the feedback loop by only allowing so many tool calls before stopping to summarize progress and check in with the user</li>
<li>Always providing three to five suggestions of what to do next</li>
</ol>
<p>With a suitably capable LLM, this combination makes data exploration feel like flying.</p>
<p>It took us months to convince ourselves that a tool like this was safe enough to ship to users, and months more to turn the prototype into production-ready software. Since this felt like such an aggressive architecture, we constrained Databot to a narrow remit: it was only for interactively exploring data. After exploration was complete, it could also export a reproducible Quarto report that records both the findings and the methods, but that was pretty much it.</p>
<p>We officially launched Databot on August 28, 2025 with <a href="https://posit.co/blog/introducing-databot" target="_blank" rel="noopener">this blog post</a>. We were still so nervous about the dangers that we published a <a href="https://posit.co/blog/databot-is-not-a-flotation-device" target="_blank" rel="noopener">companion blog post</a> at the same time, telling users how <em>not</em> to use Databot. As models have gotten better, and tens of millions of users have grown accustomed to aggressively agentic AI tools, this second blog post remains technically accurate but perhaps reads a bit alarmist. In practice, agentic AI coding tools are not completely safe, but neither are they necessarily as dangerous as they may theoretically appear. Their usefulness across a wide variety of tasks helps balance this risk.</p>
<p>Databot&rsquo;s crucial weakness turned out not to be that it was too dangerous, but too narrowly scoped. EDA does not happen in a vacuum. It&rsquo;s often done in service of some larger project, and insights derived from EDA typically immediately lead to the creation of a reproducible cleaning script, interactive Shiny app, automated data transformation job, etc. As an EDA-focused agent harness, Databot did not have the tools needed to do those tasks well.</p>
<p>This constrained nature left users feeling frustrated. Databot could be so intelligent, so facile with data exploration, yet so suddenly clumsy the moment you asked it to edit a data cleaning script, and the distinction felt arbitrary. Even if you understood Databot&rsquo;s limitations, it still left you needing to manually perform a &ldquo;hand off&rdquo; between an EDA session in Databot and a Shiny app authoring session in Positron Assistant.</p>
<h3 id="why-did-we-make-two-agents-instead-of-one">Why did we make two agents instead of one?
</h3>
<p>Given the obvious downsides of creating two distinct agents for Positron instead of focusing on one — confusion, disjointed experience, duplication of effort — why did we do it?</p>
<p>First, as stated above, Databot felt not just risky but borderline reckless in how much power we were encouraging the agent to wield. Positron Assistant had very similar behavior to Copilot, so while not totally &ldquo;safe&rdquo;, it felt like a widely understood and accepted set of tradeoffs. It felt reasonable to have one conservative agent that we could tell everyone to use in Positron Assistant, and one aggressive agent for early adopters to play with in Databot.</p>
<p>Second, Positron Assistant seemed easier to build because we could start with so much existing functionality from Copilot. For Databot, we had to build everything from scratch: the agent harness, the chat UI, loading and saving past conversations, everything, and it was hard to know in the beginning how fast we could do it and how successful we would be. Again, it felt right to have one agent that we knew we could ship quickly, and one that might take longer but would potentially give us a better long-term platform.</p>
<p>These reasons made sense to us in early 2025, but they haven&rsquo;t stood the test of time. As Databot gestated, Claude Code showed the world that powerful AI agents had a usefulness-to-risk ratio that far, far exceeded most people&rsquo;s thresholds. And forking Copilot didn&rsquo;t give us nearly the boost in development speed we expected: while it did give Positron Assistant a huge head start, it also saddled Positron Assistant with many unnecessarily complex abstractions and introduced us to the very expensive recurring task of merging the torrent of upstream Copilot changes with our extensive modifications.</p>
<p>A final reason had to do not with risk but with specialization: Databot&rsquo;s <a href="https://posit.co/blog/introducing-databot" target="_blank" rel="noopener">WEAR loop</a> felt distinct from Positron Assistant&rsquo;s generic loop, and this felt like a pretty fundamental UX difference. Once we got serious about combining the two, it turned out not to be so fundamental after all, and for the most part we feel like our next agent succeeds at &ldquo;code-switching&rdquo; as needed.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<ul>
<li><strong>Strengths:</strong> Effective agent harness; great for EDA, coding, and general-purpose agent tasks; integrated into both Positron and RStudio</li>
<li><strong>Weaknesses:</strong> Not designed for highly autonomous or highly parallel agentic work (if that&rsquo;s what you&rsquo;re into)</li>
<li><strong>Status:</strong> Now available in RStudio, in preview in Positron</li>
</ul>
<p>Posit Assistant is our newest data science agent, designed to build on what we liked most about each of the AI agents we&rsquo;ve built and used in the past:</p>
<ul>
<li>A simple agent harness with a small number of powerful and general tools</li>
<li>Prompting, tools, and UI/UX designed for the needs of data scientists</li>
<li>Full access to live R/Python sessions, including variables and plots</li>
<li>Integrated into our data science IDEs</li>
<li>A codebase designed for fast iteration</li>
<li>Extension points based on MCP and skills</li>
</ul>
<p>Crucially, we built Posit Assistant to live in both RStudio (since April 2026) and Positron (since June 2026). Not only did this finally bring an integrated agent to RStudio, where most R users still live today, but it also meant that every ounce of effort we put into improving Posit Assistant helps both RStudio and Positron users.</p>
<p>While Positron Assistant (the older one) grew out of Copilot, Posit Assistant (the new one) grew out of Databot. There was much we liked about Databot, and it didn&rsquo;t take much effort to expand its mission from the narrow task of exploratory data analysis to being a great general-purpose agent.</p>
<p>The big advantage of Positron Assistant in early 2025 turned out to be a big disadvantage by early 2026: being built on GitHub Copilot. While Copilot was arguably one of the two or three best coding agents for most of its existence, by the end of 2025 it was thoroughly outmatched by Claude Code.</p>
<p>At the risk of oversimplifying, the Copilot approach thinks the user should play a major role in deciding what context the underlying LLM gets to see. For example, Copilot and Positron Assistant have UI features that let you specify what files from your project you want to include with your next user prompt. You can also use <code>@-mentions</code> to bring in reference materials (for example, the Shiny VS Code extension lets you mention <code>@shiny</code> to bring in the Shiny docs).</p>
<p>In contrast, Claude Code&rsquo;s approach is to give an LLM access to powerful general-purpose abilities, like reading/writing/editing files and executing arbitrary bash commands, and then trusting the LLM to use them to gather the information it needs. This approach worked well when Claude Code was released in February 2025, and then dramatically improved over the course of 2025. The overall effect was that Claude Code felt significantly smarter and more engaged, even when using the same underlying language model.</p>
<p><strong>The highest compliment I can give to Posit Assistant is this: when exploring data, it feels a lot like Databot, and when coding, it feels a lot like Claude Code.</strong> And where Claude Code is a general-purpose coding agent, Posit Assistant is built specifically for people who work with data, with modes and skills for data tasks like data cleaning, Shiny app creation, and predictive modeling.</p>
<h3 id="why-did-we-call-it-posit-assistant-when-we-already-had-positron-assistant">Why did we call it &ldquo;Posit Assistant&rdquo; when we already had &ldquo;Positron Assistant&rdquo;!?
</h3>
<p>Maybe the most controversial thing about Posit Assistant is its name: Posit Assistant. First, it&rsquo;s quite dull. Second, why isn&rsquo;t it &ldquo;Posit Agent&rdquo;? Third, why would you give it almost-but-not-exactly the same name as the thing it&rsquo;s superseding?</p>
<p>These are all decisions that were made by me, Joe Cheng, and if you think they are bad decisions, then just know the Posit Assistant team agrees with you. (I stand by the name, but only just!)</p>
<p><strong>On dullness:</strong> A natively integrated AI chatbot for an IDE is table stakes, not a distinguishing feature. While we need a name today, I want users to eventually think of Posit Assistant as &ldquo;RStudio&rsquo;s chat pane&rdquo; or &ldquo;Positron&rsquo;s built-in AI&rdquo;, that is, for its identity to be subsumed into the host IDE. We believe most data practitioners will continue to use IDEs even as these models get more capable, and that the &ldquo;I&rdquo; in IDE is where we can offer real value. Instead of trying to build a new, distinct brand, I wanted there to barely be a brand at all.</p>
<p><strong>On &ldquo;Agent&rdquo;:</strong> The term &ldquo;agent&rdquo; is heavily overloaded these days. In the sense that it&rsquo;s been used so far in this article, it means <a href="https://simonwillison.net/2025/May/22/tools-in-a-loop/" target="_blank" rel="noopener">&ldquo;models using tools in a loop,&rdquo;</a> and Posit Assistant definitely fits that definition. But a lot of our customers understand the word to refer to &ldquo;models working highly autonomously for long periods of time&rdquo;, and we really wanted to avoid this connotation. Posit Assistant does have the ability to work autonomously if necessary and appropriate, but much of our harness encourages the model not to work autonomously in situations where it otherwise would.</p>
<p><strong>On Positron Assistant/Posit Assistant:</strong> This one is truly confusing, and the hardest one to defend. My defense is that the confusion is temporary. For most of Positron Assistant&rsquo;s short history, Posit Assistant didn&rsquo;t exist. For most of Posit Assistant&rsquo;s history, Positron Assistant will no longer exist. We&rsquo;re in a moment right now where we&rsquo;re mid-transition, so it&rsquo;s very confusing, but hopefully, that confusion is temporary.</p>
<p>I hope you will find this explanation to be satisfactory, and if not, at least now you know who to blame!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/images/featured.png" length="248470" type="image/png" />
    </item>
    <item>
      <title>RStudio&#39;s Top Feature Requests ... In Positron</title>
      <link>https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/</link>
      <pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/</guid>
      <dc:creator>Jonathan McPherson</dc:creator><description><![CDATA[<p>One of the most fun aspects of developing software in the open is that you don&rsquo;t have to guess what your users want; you can just let them tell you. For over a decade, <a href="https://github.com/rstudio/rstudio/issues" target="_blank" rel="noopener">RStudio&rsquo;s issue tracker</a> has been public, available for anyone to report problems or request enhancements. And you, the community, have delivered, collectively filing thousands of bug reports and feature ideas.</p>
<p>One of the <em>least</em> fun aspects, though, is that you never have time to do most (or even <em>half</em>) of the great ideas that bubble up through the community. RStudio&rsquo;s issue tracker is full of worthy requests that have been unimplemented for years, often because they would require outsized changes to the core system&rsquo;s architecture or behavior.</p>
<p>When we set out to make Positron, RStudio&rsquo;s issue tracker became a gold mine of possibilities. Because Positron has a new architecture and is built on a different platform, many of the things that we&rsquo;d never been able to do in RStudio suddenly became possible.</p>
<p>Today, we take a look at 10 of the most upvoted RStudio feature requests of all time and how those features have been incorporated into Positron. Every one of these 10 requests is on the <a href="https://github.com/rstudio/rstudio/issues?q=is%3Aissue%20state%3Aopen%20sort%3Areactions-%2B1-desc" target="_blank" rel="noopener">front page of the most-upvoted RStudio issues</a> as of this writing.</p>
<h2 id="1-fully-configurable-pane-layouts">#1: Fully configurable pane layouts
</h2>
<blockquote>
<p>Most IDEs have a somewhat more flexible system which allows for arbitrary tab arrangement, often using mouse gestures to edit configuration on the fly.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2879" target="_blank" rel="noopener">2879</a></p>
<p>This has been one of RStudio&rsquo;s most-upvoted requests for years. In Positron, this feature is largely inherited from upstream Code OSS (the open source core of VS Code). It has a very flexible layout system which allows tabs to be rearranged, split, hidden, and more with natural dragging and mouse gestures, or via context menus. Try right-clicking on any tab or divider to show places you can move it:</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/01-pane-layout.png" height="1180" width="898" alt="Customizable pane layout in Positron" />
<p>Read more about it here:</p>
<p><a href="https://code.visualstudio.com/docs/configure/custom-layout" target="_blank" rel="noopener">VS Code Layout Configuration</a></p>
<p>Positron also ships with a variety of layout presets customized for specific tasks. Try the <em>View: Stacked Layout</em> command to make it look more like RStudio, or <em>View: Notebook Layout</em> to focus on a Quarto document or Jupyter Notebook.</p>
<h2 id="2-fixed-size-graphics-device">#2: Fixed size graphics device
</h2>
<blockquote>
<p>It would be excellent if one could set the RStudio graphics device to generate plots as they would be generated by a non-interactive device with fixed dimensions, so that one could do iterative plot-making within the IDE rather than call <code>png()</code>, look at the outputs in another program, and then fiddle with font scaling, etc. and repeat.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/4422" target="_blank" rel="noopener">4422</a></p>
<p>RStudio always draws plots to fit the exact dimensions of its Plots pane, which can make it difficult to iterate on a plot you&rsquo;re preparing for publication.</p>
<p>When we added a Plots pane to Positron, we included a new tool that lets you indicate the dimensions at which you&rsquo;d like the plot to draw. You can have it fit to some common aspect ratios or &ndash; as in the RStudio feature request &ndash; specify the exact size at which you&rsquo;d like the plot to be drawn.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/02-plot-size.png" height="1016" width="1026" alt="The Plots pane with a custom size dialog" />
<p>Positron&rsquo;s Plots pane also features a visual history browser, and it remembers the code used to create the plot so you can jump to it or re-run it.</p>
<h2 id="3-multi-line-statement-support-for-python-code">#3: Multi-line statement support for Python code
</h2>
<blockquote>
<p>It would be really great to have RStudio support of multi-line statements extended to Python code as well.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/9014" target="_blank" rel="noopener">9014</a></p>
<p>One of RStudio&rsquo;s key R features is multi-line statement detection. When you tell RStudio to execute R code at your cursor location, it doesn&rsquo;t just send the current line of code; it gathers all the lines that are part of the statement and sends them together. RStudio doesn&rsquo;t know how to do this for Python, however.</p>
<p>In Positron, Python and R are peers, and we&rsquo;ve implemented multi-line statement detection for Python, too. Placing your cursor anywhere in a Python statement and invoking Run will execute the whole statement.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/03-python-multi-line.png" height="1130" width="1280" alt="A multi-line Python statement" />
<h2 id="4-tree-view-for-browsing-files-and-directories">#4: Tree view for browsing files and directories
</h2>
<blockquote>
<p>Hi, is there any plan for browsing files and directories in tree view mode?</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2552" target="_blank" rel="noopener">2552</a></p>
<p>RStudio&rsquo;s file browser lists only one directory at a time. Positron inherits Code OSS&rsquo;s tree-based explorer, which not only shows files and directories in context but even has <a href="https://code.visualstudio.com/docs/configure/themes#_file-icon-themes" target="_blank" rel="noopener">customizeable icons</a>!</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/04-file-explorer.png" alt="A tree-view file explorer" />
<h2 id="5-view-different-parts-of-a-source-file-at-the-same-time">#5: View different parts of a source file at the same time
</h2>
<blockquote>
<p>In TexShop there’s an option to split source which allows you to see (and edit) multiple areas in a document in one window so that you don’t need to keep scrolling up and down. It would be wonderful to have this as a functionality in the RStudio IDE editor</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2129" target="_blank" rel="noopener">2129</a></p>
<p>In Positron, you can have several editors open at once against the same file. With a file open, try using the <em>Split Right</em> or <em>Split Down</em> commands, which will create a second tab for the file that can be scrolled independently. You can access this via the Command Palette, or via the editor action toolbar:</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/05-split-source-file.png" alt="A single R script open in two different editor tabs" />
<h2 id="6-add-builds-for-windows-on-arm">#6: Add builds for Windows on ARM
</h2>
<blockquote>
<p>RStudio is built for Windows only for Intel (x86) processors. However, there is now a version of Windows 11 available for ARM based processors.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/11977" target="_blank" rel="noopener">11977</a></p>
<p>Microsoft has taken a page from Apple&rsquo;s playbook and started producing its own ARM-based hardware, such as the <a href="https://learn.microsoft.com/en-us/surface/surface-arm-faq" target="_blank" rel="noopener">ARM-based Surface devices</a>, and ARM-based Windows PCs are also now available from third-party manufacturers.</p>
<p>When we built Positron, we designed its native components to be easy to recompile against different CPU architectures. On Windows, Positron includes both x64 and arm64 R computation engines (kernels), so it can run natively with both x64 and arm64 versions of R. Its Python support, likewise, works great on ARM-based PCs.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/06-windows-arm.png" alt="R aarch64 running natively in Positron" />
<h2 id="7-persistent-julia-engine">#7: Persistent Julia engine
</h2>
<blockquote>
<p>It would be useful if we can use Julia in RStudio seamlessly as well as Python.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/1798" target="_blank" rel="noopener">1798</a></p>
<p>One of the main architectural goals of Positron was to make it adaptable to any language. It is a data science platform that supports &ldquo;language extensions&rdquo;. Positron&rsquo;s Python and R support subsystems are included in your Positron download, but are implemented as extensions.</p>
<p>While Posit doesn&rsquo;t currently have the expertise to develop well-rounded support for Julia, that hasn&rsquo;t stopped the community from creating a Positron extension that you can install to add Julia support. When installed, Julia becomes a peer of R and Python in Positron, and connects to Positron&rsquo;s Console, Variables, History, Packages, and Plots panes.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/07-julia-support.png" alt="Positron running a Julia session" />
<p>Find it here: <a href="https://open-vsx.org/extension/ntluong95/positron-julia" target="_blank" rel="noopener">Julia for Positron</a></p>
<p>One of the questions we get a lot is why Positron itself isn&rsquo;t a VS Code extension. This is one of the reasons why: Positron isn&rsquo;t an extension; it <em>has</em> extensions.</p>
<h2 id="8-teachingpresentation-mode">#8: Teaching/presentation mode
</h2>
<blockquote>
<p>Quite a lot of people use RStudio hooked up to a projector to teach others. It would be great if those people had a key combo that could toggle between their current theme settings and some good defaults suitable for teaching/demos. &hellip; Perhaps the ultimate goal would be to have the teaching mode itself configurable.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/4276" target="_blank" rel="noopener">4276</a></p>
<p>In Positron, it is possible to define different &ldquo;profiles&rdquo; that have different themes, font sizes, and other settings, and switch between them from the Command Palette, so it is possible set up a &ldquo;Presentation&rdquo; profile with a high-contrast theme and large text.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/08-presentation-mode.png" alt="Positron's Profile editor showing a Presentation mode" />
<p>Profiles go beyond just layouts and themes. More documentation here:</p>
<p><a href="https://code.visualstudio.com/docs/configure/profiles" target="_blank" rel="noopener">https://code.visualstudio.com/docs/configure/profiles</a></p>
<h2 id="9-fix-columns-or-rows-when-scrolling-data">#9: Fix columns or rows when scrolling data
</h2>
<blockquote>
<p>The data viewer is one of the most used tool of data analysis in RStudio. However, a very useful feature is missing. In horizontal scrolling a big table with lots of column (it&rsquo;s very common to have more columns than one screen can fit), it will be very helpful to fix the row name column so that it&rsquo;s always visible(i.e. always keep it within screen with scrolling). &hellip; More generally, sometimes we also want to fix certain columns so that it remains visible when scrolled horizontally. The row name column is just a special case of this request.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/3463" target="_blank" rel="noopener">3463</a></p>
<p>It&rsquo;s pretty common to have a column of data that represents a key value or name for the observation, or a value to which you wish to compare other values. In Positron, you can pin <em>any</em> column to fix it to the left, so that it is always visible as you scroll the other columns horizontally. Click on the column&rsquo;s action menu (vertical ellipsis) and choose <em>Pin Column</em>.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/09-pin-column.png" alt="Positron's Data Explorer with a column pinned" />
<p>You can also pin rows! Right-click on the row and choose <em>Pin Row</em>.</p>
<h2 id="10-remove-all-objects-in-the-python-environment">#10: Remove all objects in the Python environment
</h2>
<blockquote>
<p>Normally, we can do it easily in R environment by just 1 click but Python doesn&rsquo;t support it.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/8750" target="_blank" rel="noopener">8750</a></p>
<p>It&rsquo;s supported in Positron! With a Python session open in the Console, you can use the trash icon to clear all the objects from the session &ndash; in one click.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/10-python-delete-all.png" alt="The Delete All Objects feature in a Python session" />
<h2 id="-and-a-lot-more">&hellip; And a lot more.
</h2>
<p>Some of our other favorite Positron-only features that don&rsquo;t have a top-upvoted RStudio issue attached:</p>
<ul>
<li><strong>Multiple R sessions</strong>: RStudio runs only one primary R session at a time; any concurrent work needs to be done in a non-interactive background job. Positron supports multiple concurrent interactive R sessions.</li>
<li><strong>Multiple R versions</strong>: RStudio can only work with one R version at a time, and you need to switch externally using a tool like <a href="https://github.com/r-lib/rig" target="_blank" rel="noopener">rig</a>. Positron will let you choose from any R installation on your system and can even associate specific R versions with specific projects.</li>
<li><strong>Crash recovery</strong>: RStudio crashes when R does, but in Positron all you&rsquo;ll lose is the R session itself, which just gets safely restarted.</li>
<li><strong>Remote sessions</strong>: Connect remotely to another computer over SSH and run R sessions inside it, or work with reproducible projects inside <a href="https://containers.dev/" target="_blank" rel="noopener">devcontainers</a>.</li>
</ul>
<p>See <a href="https://positron.posit.co/migrate-rstudio-compare.html" target="_blank" rel="noopener">Comparing RStudio and Positron Features</a> for more.</p>
<h2 id="what-about-rstudio">What about RStudio?
</h2>
<p>While Positron&rsquo;s design has made it more practical to make many of these advancements, the point of this post isn&rsquo;t that you should switch to Positron if you&rsquo;re happy in RStudio. In fact, features are also flowing in the other direction; many of Positron&rsquo;s features are making their way back into RStudio. For example, the latest release of RStudio has:</p>
<ul>
<li>a redesigned data viewer inspired by Positron&rsquo;s data viewer;</li>
<li>code formatting optionally <a href="https://posit-dev.github.io/air/editor-rstudio.html" target="_blank" rel="noopener">powered by air</a>, Positron&rsquo;s R formatter;</li>
<li>warning/error styling in the R console inspired by Positron&rsquo;s Console; and, of course</li>
<li><a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, a platform-agnostic data analysis assistant that works in both IDEs.</li>
</ul>
<p>If you use RStudio and are interested in trying Positron, a good place to start is our <a href="https://positron.posit.co/migrate-rstudio.html" target="_blank" rel="noopener">Migrating from RStudio guide</a>.</p>
<p>Thank you for the creativity, ideas, and support you&rsquo;ve shown our IDEs over the years. Keep it coming!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/header.png" length="111055" type="image/png" />
    </item>
    <item>
      <title>Posit Assistant is specialized for data work</title>
      <link>https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/</guid>
      <dc:creator>Sara Altman</dc:creator><description><![CDATA[<p>We often get questions about how Posit Assistant compares to other coding agents like Claude Code. If you&rsquo;re already using Posit Assistant with a Claude model, are they really that different? At a high level, Claude Code is an extremely useful general-purpose coding agent, but <strong>we designed Posit Assistant specifically for people who work with data.</strong> This post covers three specific differences:</p>
<ol>
<li>Posit Assistant has out-of-the-box access to your R or Python session.</li>
<li>Posit Assistant can display plots in the chat, leading to easier interpretation for you and the agent.</li>
<li>Posit Assistant has specialized interaction modes, skills, and prompting for data analysis.</li>
</ol>
<p>You can also see a full demo of these features in this video:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/7GI6-4J0AXA"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">What's the difference between Posit Assistant, Positron Assistant, and Posit AI?</span>
</div>
<div class="callout-body">
<p>This post talks about <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, Posit&rsquo;s latest coding agent available in RStudio and Positron. <a href="https://posit.co/products/ai" target="_blank" rel="noopener">Posit AI</a> is a model subscription service that can power Posit Assistant. <a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Posit<em>ron</em> Assistant</a> is a coding agent built into Positron. We plan for Posit Assistant to supersede Positron Assistant in Q3 2026.</p>
</div>
</div>
<h3 id="coding-agents-vs-chat-apps">Coding agents vs. chat apps
</h3>
<p>Claude Code and Posit Assistant are both <em>coding agents</em>. An <em>agent</em> is a <a href="https://simonwillison.net/2025/Sep/18/agents/" target="_blank" rel="noopener">&ldquo;model running tools in a loop,&rdquo;</a> meaning an agent can see your environment, take actions in that environment (e.g., run code), and iterate based on the outcome of those actions. These abilities make agents vastly more useful for coding than chat apps like ChatGPT.</p>
<p>Chat apps can&rsquo;t see your environment or take any actions in your environment. You have to copy-and-paste code into your session, run it yourself, and paste any errors or output back. This both makes the experience more frustrating and increases the probability of errors, because the model is writing code that operates on data and files that it doesn&rsquo;t actually have access to.</p>
<p>If you haven&rsquo;t tried a coding agent before and are still relying on chat apps for coding assistance, we recommend trying one out.</p>
<h2 id="built-in-access-to-your-rpython-session">Built-in access to your R/Python session
</h2>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/pa-cc-session-access.png"
      alt="Screenshot comparing Posit Assistant and Claude Code&rsquo;s access to an R session. Posit Assistant can see session objects directly, while Claude Code must write and run scripts."  title="Posit Assistant has access to your R session out of the box." 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Posit Assistant has access to your R session out of the box.</figcaption>
  </figure></div>
</p>
<p><strong>The first major difference is that Posit Assistant has built-in access to your R or Python session.</strong> This means that Posit Assistant, without any additional configuration, can see your R or Python objects and run code directly in your active session.</p>
<p>Claude Code lacks this ability out of the box. It can write R code to files and run scripts, but it can&rsquo;t directly access data you&rsquo;ve loaded or run R code interactively.</p>
<p>If you ask a coding agent without session access to, for example, filter a tibble <code>df</code> you have in your environment, it will write R code to a script and then run that script with the <code>Rscript</code> command. This works, but has a couple of downsides:</p>
<ol>
<li>Before running the script, the agent can&rsquo;t actually tell that <code>df</code> exists. If it doesn&rsquo;t exist, it will have to wait for an error message from the script.</li>
<li>You might not want to create and run scripts for every bit of code.</li>
<li>It leaves you and the agent on different footing: you have direct access to your session, but the agent doesn&rsquo;t. This can make errors more likely and the general experience more frustrating.</li>
</ol>
<p>It is possible to give agents like Claude Code access to your R or Python session by using an MCP server. In the walkthrough video, we show how to do this using the <a href="https://github.com/posit-dev/btw" target="_blank" rel="noopener">btw package</a>, giving Claude Code the ability to access our R session and run R code directly.</p>
<p>Even with an MCP server, however, it may be difficult to see the code being run on your behalf. For some tasks, this might not matter, but if you&rsquo;re analyzing data it can be important to understand the analysis in detail. For data analysis tasks, Posit Assistant is designed for auditability and transparency. The code is visible in the tool call UI with syntax highlighting and styling, so you can easily audit what&rsquo;s happening to your data at each step.</p>
<p>See this comparison in the video: <a href="https://youtu.be/7GI6-4J0AXA?t=220" target="_blank" rel="noopener">Claude Code</a> | <a href="https://youtu.be/7GI6-4J0AXA?t=424" target="_blank" rel="noopener">Posit Assistant</a></p>
<h2 id="data-visualization">Data visualization
</h2>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/pa-plots.png"
      alt="Screenshot of Posit Assistant in RStudio displaying a plot directly in the chat panel, as well as in the plots pane, alongside the code that generated it."  title="Posit Assistant displays plots directly in the chat, making them visible to both you and the model." 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Posit Assistant displays plots directly in the chat, making them visible to both you and the model.</figcaption>
  </figure></div>
</p>
<p>Claude Code can write code to create plots, but can&rsquo;t display them natively in the terminal and will need an alternative way to show them to you (e.g., opening a file in Preview). Even for the model itself to see the plot, it has to save the plot to a PNG file and then read that file.</p>
<p><strong>Because Posit Assistant runs code in your console, plots show up in the RStudio or Positron plot pane, as well as directly in the chat panel. This makes it easier for you to quickly inspect and analyze the plot and iterate if needed.</strong> Posit Assistant will also automatically see the plot image and typically interpret it or assess whether it is correct, which makes it very useful for EDA and data analysis.</p>
<p>This ability for you and the agent to both see the same plot is important for auditability. There&rsquo;s evidence that even the most advanced models <a href="https://simonpcouch.github.io/bluffbench/" target="_blank" rel="noopener">sometimes misinterpret plots that contradict their expectations</a>, and so having you and the agent looking at and interpreting the same plot makes it easier to catch mistakes.</p>
<p>See this comparison in the video: <a href="https://youtu.be/7GI6-4J0AXA?t=560" target="_blank" rel="noopener">Claude Code</a> | <a href="https://youtu.be/7GI6-4J0AXA?t=703" target="_blank" rel="noopener">Posit Assistant</a></p>
<h2 id="specialized-data-analysis-capabilities">Specialized data analysis capabilities
</h2>
<p>Session access and plotting affordances both directly support data analysis, but Posit Assistant also has interaction modes, prompting, and skills specifically designed for it.</p>
<p>Posit Assistant is both a general-purpose coding agent and a specialized data analysis agent, and it behaves slightly differently depending on your task. During data exploration, it only runs a few bits of code at a time, then summarizes what it found and suggests next steps. Because the purpose of data analysis, especially exploration, is for you to learn about the data, the entire process breaks down if the model runs ahead with analysis you can&rsquo;t keep up with. <strong>Posit Assistant&rsquo;s approach to co-analysis with the user is designed to support your understanding of the data rather than hinder it.</strong></p>
<figure>
<img src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/pa-data-analysis.gif" alt="GIF of Posit Assistant carrying out some data analysis on the likeliness words data. Posit Assistant does three rounds of tool calls, summarizes what it found, and provides some suggestions." width="600px" />
<figcaption>Posit Assistant explores data iteratively, running a few bits of code at a time before summarizing and suggesting next steps.</figcaption>
</figure>
<p>Claude Code excels at writing code, but is generally oriented toward writing that code to files, executing it, and reviewing it, rather than exploring data step-by-step with the user.</p>
<p><strong>Posit Assistant also has specialized prompting, skills, and tools that promote sound data analysis.</strong> These include skills for making Shiny apps and Quarto reports, a <a href="https://opensource.posit.co/blog/2026-05-08_ai-newsletter/" target="_blank" rel="noopener">data cleaning mode</a>, and prompting about rigorous statistical and modeling practices.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/data-cleaning-mode.png"
      alt="Screenshot of Posit Assistant&rsquo;s data cleaning mode, showing a tabbed interface with issues like &lsquo;Sparse early/late years&rsquo;. The assistant presents a table of row counts by year range, explains the imbalance, and offers actionable choices: keep all years, filter to 2011–2022, or filter to 2011–2019."  title="Posit Assistant in data cleaning mode, surfacing questions to the user." 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Posit Assistant in data cleaning mode, surfacing questions to the user.</figcaption>
  </figure></div>
</p>
<p>See this comparison in the video: <a href="https://youtu.be/7GI6-4J0AXA?t=990" target="_blank" rel="noopener">Claude Code</a> | <a href="https://youtu.be/7GI6-4J0AXA?t=1133" target="_blank" rel="noopener">Posit Assistant</a></p>
<h2 id="posit-assistant-is-designed-for-data-work">Posit Assistant is designed for data work
</h2>
<p>Claude Code, Codex, and other coding agents are very useful tools. They excel at general-purpose coding tasks, and you might want to use them for work that benefits from running multiple agents in parallel, or because you want to take advantage of your existing subscription plans.</p>
<p>However, we made Posit Assistant because we think the millions of data scientists, researchers, and analysts who rely on R and Python should have an excellent agent specifically designed for them and their needs. Those needs include data analysis and visualization, but also package development, Shiny app creation, and other coding-focused tasks in the data science ecosystem.</p>
<p>Learn more about Posit Assistant: <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">https://posit-dev.github.io/assistant/</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-08_comparing-posit-assistant-and-claude-code/images/featured.png" length="134058" type="image/png" />
    </item>
    <item>
      <title>Positron June Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p><a href="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/" target="_blank" rel="noopener">Last release</a> we introduced <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a>, our unified, data-science-focused approach to AI assistance. This release continues that work, and we want to give you advance notice that the older Positron Assistant will be deprecated in the next release. If you are still using Positron Assistant, we encourage you to migrate to Posit Assistant now.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/posit-assistant.gif" data-fig-align="center" data-fig-alt="Posit Assistant in Positron, Posit&#39;s unified data-science AI assistant, which is replacing the older Positron Assistant." />
<div class="callout callout-important" role="note" aria-label="Important">
<div class="callout-header">
<span class="callout-title">Important</span>
</div>
<div class="callout-body">
<p>During this migration period, you will likely want to keep both the old <a href="positron://settings/positron.assistant.enable"><code>positron.assistant.enable</code></a> setting and the new <a href="positron://settings/assistant.enabled"><code>assistant.enabled</code></a> set to true for best functionality, but do be aware that the older setting is in the process of being deprecated.</p>
</div>
</div>
<p>Posit Assistant supports the same broad set of providers as Positron Assistant, along with the Posit AI model provider and new experimental support for Google Vertex. Learn more about the differences between the new Posit Assistant and the older Positron Assistant:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/Y9P2nlFXKnQ"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<p>You may also be interested in <a href="https://youtu.be/7GI6-4J0AXA" target="_blank" rel="noopener">comparing Posit Assistant and Claude Code</a>.</p>
<p>New in Posit Assistant this release, you can use the Posit AI model provider for Next Edit Suggestions, which propose your likely next change as you edit. You can also access the <strong>Configure Language Model Providers</strong> item in the accounts menu. You can <a href="https://github.com/posit-dev/assistant-feedback" target="_blank" rel="noopener">ask a question, report a bug, or request a new feature</a> for Posit Assistant separately from Positron now, but don&rsquo;t worry too much about where to send your feedback; we will help route it to the right place!</p>
<h2 id="packages-pane-improvements">Packages pane improvements
</h2>
<p>We introduced the <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages</a> pane last release, and this release makes it more informative and flexible. A <strong>Show Help</strong> button and context menu entry on every package take you straight to its documentation in the <a href="https://positron.posit.co/help-pane" target="_blank" rel="noopener">Help pane</a>. You can now combine category filters as an intersection, so you can narrow the list to, for example, packages that are both attached and outdated. A new <strong>Item Size</strong> toggle lets you switch between a compact row view and a richer card view that surfaces package descriptions and other metadata.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/packages-pane.gif" data-fig-align="center" data-fig-alt="The Positron Packages pane showing the attached category filter, the Item Size toggle switching between compact rows and a card view with package descriptions, and how to update a package." />
<p>For R users, the new <a href="positron://settings/packages.r.installer"><code>packages.r.installer</code></a> setting controls whether installs, updates, and removals use pak, base R, or an automatic choice. The setting that controls the pane has been renamed to <a href="positron://settings/packages.enabled"><code>packages.enabled</code></a>; the previous <code>positron.packages.enable</code> setting is deprecated but still honored.</p>
<h2 id="inline-output-for-quarto">Inline output for Quarto
</h2>
<p><a href="https://positron.posit.co/quarto-inline-output" target="_blank" rel="noopener">Inline output for <code>.qmd</code> documents</a> was one of Positron&rsquo;s most-requested features ever, and it is now out of preview. A new toolbar button in Quarto documents gives you one-click access to running cells, managing inline output, and showing the console.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/inline-output.gif" data-fig-align="center" data-fig-alt="A Quarto document in Positron showing inline output beneath an executed code cell, with the new toolbar button for managing output." />
<p>Inserting code into a Quarto document from the History pane now automatically wraps it in cell markup when you drop it into a prose region. You can opt into inline output with the <a href="positron://settings/positron.quarto.inlineOutput.enabled"><code>positron.quarto.inlineOutput.enabled</code></a> setting.</p>
<h2 id="faster-startup">Faster startup
</h2>
<p>We continue to invest in performance improvements, and Positron starts up even faster in this release. Positron now caches previously discovered system interpreters, dramatically speeding up startup in new folders and projects; you can control this with the new <a href="positron://settings/interpreters.discoveryCache.enabled"><code>interpreters.discoveryCache.enabled</code></a> setting. On Windows, we fixed multi-minute startup delays by skipping slow <code>PATH</code> discovery by default, governed by the new <a href="positron://settings/positron.r.interpreters.pathDiscoveryMode"><code>positron.r.interpreters.pathDiscoveryMode</code></a> setting. We also fixed an occasional hang at &ldquo;Preparing&rdquo; when starting Positron after updating to a new version.</p>
<h2 id="a-more-customizable-interface">A more customizable interface
</h2>
<p>This release gives you finer control over Positron&rsquo;s chrome so you can tailor the interface to your workflow. Several layout settings that previously weren&rsquo;t always honored now work as expected:</p>
<ul>
<li><a href="positron://settings/workbench.topActionBar.visible"><code>workbench.topActionBar.visible</code></a> reliably hides and shows the top action bar at runtime</li>
<li><a href="positron://settings/workbench.secondarySideBar.defaultVisibility"><code>workbench.secondarySideBar.defaultVisibility</code></a> is respected when you set it explicitly</li>
<li><a href="positron://settings/workbench.secondarySideBar.showLabel"><code>workbench.secondarySideBar.showLabel</code></a> lets Secondary Side Bar items render as compact icons</li>
</ul>
<p>You can also enable <a href="positron://settings/window.commandCenter"><code>window.commandCenter</code></a> to bring the command center into the title bar. Learn more about some of our own team members&rsquo; favorite customizations:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/QIYyeuZ_ISY"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<h2 id="r-language-intelligence">R language intelligence
</h2>
<p>Positron&rsquo;s R language support now understands symbols within a file. You can rename a local symbol and have every use of it updated at once, and Go to Definition and Find References now work for local symbols, so you can jump to where a variable is defined (such as via <code>&lt;-</code> or a function parameter) or see everywhere it is used within a function. Renaming symbols across files is not yet supported, but it is coming soon.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>We have some exciting milestones planned for our July release! The <a href="https://positron.posit.co/positron-notebook-editor.html" target="_blank" rel="noopener">new notebook editor for <code>.ipynb</code> files</a>, the <a href="https://positron.posit.co/packages-pane.html" target="_blank" rel="noopener">Packages pane</a>, and <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a> will all come out of preview to general availability. We are thrilled for these features to be ready for your production workflows, and we hope you will give them a try and <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">share your feedback</a>.</li>
<li>We&rsquo;re still prototyping first-class SQL editing and execution in Positron, including support for visualizations with <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release/" target="_blank" rel="noopener">ggsql</a>. We&rsquo;ll share more as this takes shape, and in the meantime, <a href="https://github.com/posit-dev/positron/issues/7233" target="_blank" rel="noopener">let us know</a> your thoughts and current pain points!</li>
<li>If you will be at the Databricks Data + AI Summit next week, <a href="https://posit.co/events/databricks-data-ai-summit-2026" target="_blank" rel="noopener">join us</a> to learn about using Positron and Workbench within the Databricks platform.</li>
<li>We are looking forward to posit::conf(2026) in September, where our team will have several sessions on Positron. The <a href="https://posit.co/blog/posit-conf-2026-agenda-breakdown" target="_blank" rel="noopener">conference program</a> was released last week and we are pretty excited about everything happening! <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/featured.png" length="73388" type="image/png" />
    </item>
    <item>
      <title>Libraries for your Python Polars workflows</title>
      <link>https://opensource.posit.co/blog/2026-06-04_libraries-for-python-polars/</link>
      <pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-04_libraries-for-python-polars/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<script src="https://cdn.jsdelivr.net/npm/requirejs@2.3.6/require.min.js" integrity="sha384-c9c+LnTbwQ3aujuU7ULEPVvgLs+Fn6fJUvIGTsuu1ZcCf11fiEubah0ttpca4ntM sha384-6V1/AdqZRWk1KAlWbKBlGhN7VG4iE/yAZcO6NZPMF8od0vukrvr0tg4qY6NSrItx" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2" crossorigin="anonymous" data-relocate-top="true"></script>
<script type="application/javascript">define('jquery', [],function() {return window.jQuery;})</script>
<p>We (Emil Hvitfeldt, Jeroen Janssens, Michael Chow, and I) just got back from PyCon US 2026, and there was <strong>so much buzz</strong> around <a href="https://pola.rs/" target="_blank" rel="noopener">Polars</a>. What is Polars? Why should I switch to Polars? <em>How</em> do I switch to Polars?</p>
<p>I mean, just check out the line for the book signing of Jeroen&rsquo;s book, <a href="https://polarsguide.com/" target="_blank" rel="noopener">Python Polars: The Definitive Guide</a>.</p>
<img src="https://opensource.posit.co/blog/2026-06-04_libraries-for-python-polars/images/book-signing.jpeg" data-fig-alt="A long line of conference attendees waiting at Jeroen&#39;s book signing booth in PyCon US&#39; large convention hall" />
<p>If Polars is new to you, it is a library for efficient data manipulation in Python. It&rsquo;s built on Rust, so it&rsquo;s super fast. And a lot of people (including <a href="https://polarsguide.com/praise/" target="_blank" rel="noopener">the creator of Pandas</a>!) like the intuitive way you write Polars code. However, if you work in Python, you might know that different DataFrames have different requirements, so you want to make sure to use libraries that support Polars (I admit, I come from the R world, and this was mind blowing to me).</p>
<p>And, we&rsquo;re happy to say that we (Posit) have excellent Polars support across our Python libraries for every stage of the data science workflow! In this post, we&rsquo;ll walk through four:</p>
<ul>
<li><a href="https://posit-dev.github.io/pointblank/" target="_blank" rel="noopener"><strong>pointblank</strong></a> for data validation and quality checks</li>
<li><a href="https://posit-dev.github.io/great-tables/articles/intro.html" target="_blank" rel="noopener"><strong>Great Tables</strong></a> for creating publication-quality tables</li>
<li><a href="https://plotnine.org/" target="_blank" rel="noopener"><strong>plotnine</strong></a> for ggplot2-style visualizations</li>
<li><a href="https://mlverse.github.io/mall/" target="_blank" rel="noopener"><strong>mall</strong></a> for LLM-powered data analysis</li>
</ul>
<p>Let&rsquo;s check them out!</p>
<h2 id="setup">Setup
</h2>
<p>First, let&rsquo;s install the libraries:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">pip</span> <span class="n">install</span> <span class="n">polars</span> <span class="n">great_tables</span> <span class="n">pointblank</span> <span class="n">plotnine</span> <span class="n">mlverse</span><span class="o">-</span><span class="n">mall</span></span></span></code></pre></div></div>
<p>Now, import what we need:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polars</span> <span class="k">as</span> <span class="nn">pl</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">GT</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pointblank</span> <span class="k">as</span> <span class="nn">pb</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">plotnine</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ggplot</span><span class="p">,</span> <span class="n">aes</span><span class="p">,</span> <span class="n">geom_point</span><span class="p">,</span> <span class="n">geom_line</span><span class="p">,</span> <span class="n">geom_bar</span><span class="p">,</span> <span class="n">geom_text</span><span class="p">,</span> <span class="n">geom_col</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">labs</span><span class="p">,</span> <span class="n">theme_minimal</span><span class="p">,</span> <span class="n">theme</span><span class="p">,</span> <span class="n">element_text</span><span class="p">,</span> <span class="n">scale_fill_manual</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">scale_y_continuous</span><span class="p">,</span> <span class="n">element_rect</span><span class="p">,</span> <span class="n">element_blank</span><span class="p">,</span> <span class="n">element_line</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">scale_color_manual</span><span class="p">,</span> <span class="n">position_dodge</span><span class="p">,</span> <span class="n">annotate</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">mall</span></span></span></code></pre></div></div>
<h2 id="the-dataset">The dataset
</h2>
<p>We&rsquo;ll demonstrate these tools using <code>sales_data</code>, a sample sales dataset:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">sales_data</span> <span class="o">=</span> <span class="n">pl</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;date&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-15&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-16&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-18&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-19&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-20&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;2026-01-21&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;region&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;product&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget A&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget A&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget A&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Widget C&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sales&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">1800</span><span class="p">,</span> <span class="mi">1500</span><span class="p">,</span> <span class="mi">2100</span><span class="p">,</span> <span class="mi">1650</span><span class="p">,</span> <span class="mi">1900</span><span class="p">,</span> <span class="mi">2300</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;units_sold&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">24</span><span class="p">,</span> <span class="mi">36</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="mi">46</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;customer_rating&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">4.5</span><span class="p">,</span> <span class="mf">4.8</span><span class="p">,</span> <span class="mf">4.6</span><span class="p">,</span> <span class="mf">4.9</span><span class="p">,</span> <span class="mf">4.7</span><span class="p">,</span> <span class="mf">4.8</span><span class="p">,</span> <span class="mf">4.9</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="o">.</span><span class="n">with_columns</span><span class="p">(</span><span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;date&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">str</span><span class="o">.</span><span class="n">strptime</span><span class="p">(</span><span class="n">pl</span><span class="o">.</span><span class="n">Date</span><span class="p">,</span> <span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sales_data</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>shape: (7, 6)
┌────────────┬────────┬──────────┬───────┬────────────┬─────────────────┐
│ date       ┆ region ┆ product  ┆ sales ┆ units_sold ┆ customer_rating │
│ ---        ┆ ---    ┆ ---      ┆ ---   ┆ ---        ┆ ---             │
│ date       ┆ str    ┆ str      ┆ i64   ┆ i64        ┆ f64             │
╞════════════╪════════╪══════════╪═══════╪════════════╪═════════════════╡
│ 2026-01-15 ┆ North  ┆ Widget A ┆ 1200  ┆ 24         ┆ 4.5             │
│ 2026-01-16 ┆ South  ┆ Widget B ┆ 1800  ┆ 36         ┆ 4.8             │
│ 2026-01-17 ┆ North  ┆ Widget A ┆ 1500  ┆ 30         ┆ 4.6             │
│ 2026-01-18 ┆ West   ┆ Widget C ┆ 2100  ┆ 42         ┆ 4.9             │
│ 2026-01-19 ┆ South  ┆ Widget B ┆ 1650  ┆ 33         ┆ 4.7             │
│ 2026-01-20 ┆ North  ┆ Widget A ┆ 1900  ┆ 38         ┆ 4.8             │
│ 2026-01-21 ┆ West   ┆ Widget C ┆ 2300  ┆ 46         ┆ 4.9             │
└────────────┴────────┴──────────┴───────┴────────────┴─────────────────┘
</code></pre>
<p>We can confirm that it is, indeed, a Polars DataFrame!</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">isinstance</span><span class="p">(</span><span class="n">sales_data</span><span class="p">,</span> <span class="n">pl</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>True
</code></pre>
<h2 id="data-validation-with-pointblank">Data validation with pointblank
</h2>
<p>Let&rsquo;s now validate our data quality using <a href="https://posit-dev.github.io/pointblank/" target="_blank" rel="noopener"><strong>pointblank</strong></a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">pb</span><span class="o">.</span><span class="n">Validate</span><span class="p">(</span><span class="n">sales_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">col_vals_not_null</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;date&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">col_vals_between</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;sales&#34;</span><span class="p">,</span> <span class="n">left</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">col_vals_between</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;customer_rating&#34;</span><span class="p">,</span> <span class="n">left</span><span class="o">=</span><span class="mf">1.0</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mf">5.0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">col_vals_in_set</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;region&#34;</span><span class="p">,</span> <span class="nb">set</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;North&#34;</span><span class="p">,</span> <span class="s2">&#34;South&#34;</span><span class="p">,</span> <span class="s2">&#34;East&#34;</span><span class="p">,</span> <span class="s2">&#34;West&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">interrogate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">agent</span></span></span></code></pre></div></div>
<div id="pb_tbl" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap');
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans&display=swap');
#pb_tbl table {
          font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }
#pb_tbl thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#pb_tbl p { margin: 0; padding: 0; }
 #pb_tbl .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 90%; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #pb_tbl .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #pb_tbl .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #pb_tbl .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #pb_tbl .gt_heading { background-color: #FFFFFF; text-align: left; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #pb_tbl .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #pb_tbl .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #pb_tbl .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #pb_tbl .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #pb_tbl .gt_column_spanner_outer:first-child { padding-left: 0; }
 #pb_tbl .gt_column_spanner_outer:last-child { padding-right: 0; }
 #pb_tbl .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #pb_tbl .gt_spanner_row { border-bottom-style: hidden; }
 #pb_tbl .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #pb_tbl .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #pb_tbl .gt_from_md> :first-child { margin-top: 0; }
 #pb_tbl .gt_from_md> :last-child { margin-bottom: 0; }
 #pb_tbl .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #pb_tbl .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #pb_tbl .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #pb_tbl .gt_row_group_first td { border-top-width: 2px; }
 #pb_tbl .gt_row_group_first th { border-top-width: 2px; }
 #pb_tbl .gt_striped { color: #333333; background-color: #F4F4F4; }
 #pb_tbl .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #pb_tbl .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; }
 #pb_tbl .gt_first_grand_summary_row_bottom { border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; }
 #pb_tbl .gt_last_grand_summary_row_top { border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; }
 #pb_tbl .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #pb_tbl .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #pb_tbl .gt_left { text-align: left; }
 #pb_tbl .gt_center { text-align: center; }
 #pb_tbl .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #pb_tbl .gt_font_normal { font-weight: normal; }
 #pb_tbl .gt_font_bold { font-weight: bold; }
 #pb_tbl .gt_font_italic { font-style: italic; }
 #pb_tbl .gt_super { font-size: 65%; }
 #pb_tbl .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #pb_tbl .gt_asterisk { font-size: 100%; vertical-align: 0; }
</style>
<table style="table-layout: fixed;; width: 0px" class="gt_table" data-quarto-disable-processing="true" data-quarto-bootstrap="false">
<colgroup>
  <col style="width:4px;"/>
  <col style="width:35px;"/>
  <col style="width:190px;"/>
  <col style="width:120px;"/>
  <col style="width:120px;"/>
  <col style="width:50px;"/>
  <col style="width:50px;"/>
  <col style="width:60px;"/>
  <col style="width:60px;"/>
  <col style="width:60px;"/>
  <col style="width:30px;"/>
  <col style="width:30px;"/>
  <col style="width:30px;"/>
  <col style="width:65px;"/>
</colgroup>
<thead>
  <tr class="gt_heading">
    <td colspan="14" class="gt_heading gt_title gt_font_normal" style="color: #444444;font-size: 28px;text-align: left;font-weight: bold; text-align: left;">Pointblank Validation</td>
  </tr>
  <tr class="gt_heading">
    <td colspan="14" class="gt_heading gt_subtitle gt_font_normal gt_bottom_border" style="text-align: left;"><div><span style='text-decoration-style: solid; text-decoration-color: #ADD8E6; text-decoration-line: underline; text-underline-position: under; color: #333333; font-variant-numeric: tabular-nums; padding-left: 4px; margin-right: 5px; padding-right: 2px;'>2026-06-04|17:00:11</span><div style="padding-top: 10px; padding-bottom: 5px;"><span style='background-color: #0075FF; color: #FFFFFF; padding: 0.5em 0.5em; position: inherit; text-transform: uppercase; margin: 5px 10px 5px 0px; border: solid 1px #0075FF; font-weight: bold; padding: 2px 10px 2px 10px; font-size: 10px;'>Polars</span></div></div></td>
  </tr>
<tr class="gt_col_headings">
  <th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-status_color"></th>
  <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-i"></th>
  <th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-type_upd">STEP</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-columns_upd">COLUMNS</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-values_upd">VALUES</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-tbl">TBL</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-eval">EVAL</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-test_units">UNITS</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-pass">PASS</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-fail">FAIL</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-w_upd">W</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-e_upd">E</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-c_upd">C</th>
  <th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" style="color: #666666;font-weight: bold;" scope="col" id="pb_tbl-extract_upd">EXT</th>
</tr>
</thead>
<tbody class="gt_table_body">
  <tr>
    <td style="height: 40px; background-color: #4CA64C; color: transparent;font-size: 0px;" class="gt_row gt_left">#4CA64C</td>
    <td style="height: 40px; color: #666666;font-size: 13px;font-weight: bold;" class="gt_row gt_right">1</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_left">
        <div style="margin: 0; padding: 0; display: inline-block; height: 30px; vertical-align: middle; width: 16%;">
            <!--?xml version="1.0" encoding="UTF-8"?--><?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 67 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>col_vals_not_null</title>
    <g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="col_vals_not_null" transform="translate(0.000000, 0.551724)">
            <path d="M56.712234,1 C59.1975153,1 61.4475153,2.00735931 63.076195,3.63603897 C64.7048747,5.26471863 65.712234,7.51471863 65.712234,10 L65.712234,10 L65.712234,65 L10.712234,65 C8.22695259,65 5.97695259,63.9926407 4.34827294,62.363961 C2.71959328,60.7352814 1.71223397,58.4852814 1.71223397,56 L1.71223397,56 L1.71223397,10 C1.71223397,7.51471863 2.71959328,5.26471863 4.34827294,3.63603897 C5.97695259,2.00735931 8.22695259,1 10.712234,1 L10.712234,1 Z" id="rectangle" stroke="#000000" stroke-width="2" fill="#FFFFFF"></path>
            <path d="M40.6120805,47.037834 C37.4692348,47.037834 35.0126139,45.9348613 33.712234,44.0140597 C32.4118541,45.9348613 29.9552331,47.037834 26.8123883,47.037834 C22.6574397,47.037834 16.0646712,43.4437723 16.0646712,33.8021619 C16.0646712,29.3401361 17.4715879,18.962166 30.5035862,18.962166 C30.9454018,18.962166 31.3057481,19.3225124 31.3057481,19.7643279 L31.3057481,21.3686518 C31.3057481,21.8104674 30.9454018,22.1708138 30.5035862,22.1708138 C26.6400486,22.1708138 22.4819668,25.8118774 22.4819668,33.8021619 C22.4819668,37.5090277 23.7635456,43.0270243 27.2949384,43.0270243 C29.795428,43.0270243 31.224279,40.4231312 32.0985095,38.2861221 C30.5067194,35.6101596 29.7014243,33.1034035 29.7014243,30.8347892 C29.7014243,25.6238707 31.8603677,23.7751377 33.712234,23.7751377 C35.5641002,23.7751377 37.7230437,25.6238707 37.7230437,30.8347892 C37.7230437,33.1347383 36.9396828,35.5788255 35.3290916,38.2861221 C36.6294715,41.4321009 38.243196,43.0270243 40.1295295,43.0270243 C43.6609223,43.0270243 44.9425012,37.5090277 44.9425012,33.8021619 C44.9425012,25.8118774 40.7844193,22.1708138 36.9208817,22.1708138 C36.4759329,22.1708138 36.1187198,21.8104674 36.1187198,21.3686518 L36.1187198,19.7643279 C36.1187198,19.3225124 36.4759329,18.962166 36.9208817,18.962166 C49.9528801,18.962166 51.3597967,29.3401361 51.3597967,33.8021619 C51.3597967,43.4437723 44.7670282,47.037834 40.6120805,47.037834 Z" id="omega" fill="#000000" fill-rule="nonzero"></path>
            <path d="M33,7.93597705 C33.2761424,7.93597705 33.5,8.15983467 33.5,8.43597705 L33.5,57.564023 C33.5,57.8401653 33.2761424,58.064023 33,58.064023 C32.7238576,58.064023 32.5,57.8401653 32.5,57.564023 L32.5,8.43597705 C32.5,8.15983467 32.7238576,7.93597705 33,7.93597705 Z" id="line_black" fill="#000000" transform="translate(33.000000, 33.000000) rotate(-320.000000) translate(-33.000000, -33.000000) "></path>
            <polygon id="line_white" fill="#FFFFFF" transform="translate(34.899496, 32.153303) rotate(-320.000000) translate(-34.899496, -32.153303) " points="34.3994962 8.54160469 35.3994962 8.54160469 35.3994962 55.7650019 34.3994962 55.7650019"></polygon>
        </g>
    </g>
</svg>
        </div>
        <div style="font-family: 'IBM Plex Mono', monospace, courier; color: black; font-size: 10px; display: inline-block; vertical-align: middle;">
            <div>col_vals_not_null()</div>
        </div>
        </td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">date</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center"><svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="vertical-align: middle;">
    <g id="unchanged" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="unchanged" transform="translate(0.500000, 0.570147)">
            <rect id="Rectangle" x="0.125132506" y="0" width="23.749735" height="23.7894737"></rect>
            <path d="M5.80375046,8.18194736 C3.77191832,8.18194736 2.11875046,9.83495328 2.11875046,11.8669474 C2.11875046,13.8989414 3.77191832,15.5519474 5.80375046,15.5519474 C7.8355826,15.5519474 9.48875046,13.8989414 9.48875046,11.8669474 C9.48875046,9.83495328 7.83552863,8.18194736 5.80375046,8.18194736 Z M5.80375046,14.814915 C4.17821997,14.814915 2.85578285,13.4924778 2.85578285,11.8669474 C2.85578285,10.2414169 4.17821997,8.91897975 5.80375046,8.91897975 C7.42928095,8.91897975 8.75171807,10.2414169 8.75171807,11.8669474 C8.75171807,13.4924778 7.42928095,14.814915 5.80375046,14.814915 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
            <path d="M13.9638189,8.699335 C13.9364621,8.70430925 13.9091059,8.71176968 13.8842359,8.71923074 C13.7822704,8.73663967 13.6877654,8.77643115 13.6056956,8.83860518 L10.2433156,11.3852598 C10.0766886,11.5046343 9.97720993,11.6986181 9.97720993,11.9025491 C9.97720993,12.1064807 10.0766886,12.3004639 10.2433156,12.4198383 L13.6056956,14.966493 C13.891697,15.1803725 14.2970729,15.1231721 14.5109517,14.8371707 C14.7248313,14.5511692 14.6676309,14.145794 14.3816294,13.9319145 L12.5313257,12.5392127 L21.8812495,12.5392127 L21.8812495,11.2658854 L12.5313257,11.2658854 L14.3816294,9.87318364 C14.6377872,9.71650453 14.7497006,9.40066014 14.6477351,9.11714553 C14.5482564,8.83363156 14.262255,8.65954352 13.9638189,8.699335 Z" id="arrow" fill="#000000" transform="translate(15.929230, 11.894737) rotate(-180.000000) translate(-15.929230, -11.894737) "></path>
        </g>
    </g>
</svg></td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center"><span style="color:#4CA64C;">&check;</span></td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_right">7</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">7<br />1.00</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">0<br />0.00</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px;" class="gt_row gt_center">—</td>
  </tr>
  <tr>
    <td style="height: 40px; background-color: #4CA64C; color: transparent;font-size: 0px;" class="gt_row gt_left">#4CA64C</td>
    <td style="height: 40px; color: #666666;font-size: 13px;font-weight: bold;" class="gt_row gt_right">2</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_left">
        <div style="margin: 0; padding: 0; display: inline-block; height: 30px; vertical-align: middle; width: 16%;">
            <!--?xml version="1.0" encoding="UTF-8"?--><?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 67 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>col_vals_between</title>
    <g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="col_vals_between" transform="translate(0.000000, 0.206897)">
            <path d="M56.712234,1 C59.1975153,1 61.4475153,2.00735931 63.076195,3.63603897 C64.7048747,5.26471863 65.712234,7.51471863 65.712234,10 L65.712234,10 L65.712234,65 L10.712234,65 C8.22695259,65 5.97695259,63.9926407 4.34827294,62.363961 C2.71959328,60.7352814 1.71223397,58.4852814 1.71223397,56 L1.71223397,56 L1.71223397,10 C1.71223397,7.51471863 2.71959328,5.26471863 4.34827294,3.63603897 C5.97695259,2.00735931 8.22695259,1 10.712234,1 L10.712234,1 Z" id="rectangle" stroke="#000000" stroke-width="2" fill="#FFFFFF"></path>
            <path d="M11.993484,21.96875 C10.962234,22.082031 10.188797,22.964844 10.212234,24 L10.212234,42 C10.200515,42.722656 10.579422,43.390625 11.204422,43.753906 C11.825515,44.121094 12.598953,44.121094 13.220047,43.753906 C13.845047,43.390625 14.223953,42.722656 14.212234,42 L14.212234,24 C14.220047,23.457031 14.009109,22.9375 13.626297,22.554688 C13.243484,22.171875 12.723953,21.960938 12.180984,21.96875 C12.118484,21.964844 12.055984,21.964844 11.993484,21.96875 Z M55.993484,21.96875 C54.962234,22.082031 54.188797,22.964844 54.212234,24 L54.212234,42 C54.200515,42.722656 54.579422,43.390625 55.204422,43.753906 C55.825515,44.121094 56.598953,44.121094 57.220047,43.753906 C57.845047,43.390625 58.223953,42.722656 58.212234,42 L58.212234,24 C58.220047,23.457031 58.009109,22.9375 57.626297,22.554688 C57.243484,22.171875 56.723953,21.960938 56.180984,21.96875 C56.118484,21.964844 56.055984,21.964844 55.993484,21.96875 Z M16.212234,22 C15.661453,22 15.212234,22.449219 15.212234,23 C15.212234,23.550781 15.661453,24 16.212234,24 C16.763015,24 17.212234,23.550781 17.212234,23 C17.212234,22.449219 16.763015,22 16.212234,22 Z M20.212234,22 C19.661453,22 19.212234,22.449219 19.212234,23 C19.212234,23.550781 19.661453,24 20.212234,24 C20.763015,24 21.212234,23.550781 21.212234,23 C21.212234,22.449219 20.763015,22 20.212234,22 Z M24.212234,22 C23.661453,22 23.212234,22.449219 23.212234,23 C23.212234,23.550781 23.661453,24 24.212234,24 C24.763015,24 25.212234,23.550781 25.212234,23 C25.212234,22.449219 24.763015,22 24.212234,22 Z M28.212234,22 C27.661453,22 27.212234,22.449219 27.212234,23 C27.212234,23.550781 27.661453,24 28.212234,24 C28.763015,24 29.212234,23.550781 29.212234,23 C29.212234,22.449219 28.763015,22 28.212234,22 Z M32.212234,22 C31.661453,22 31.212234,22.449219 31.212234,23 C31.212234,23.550781 31.661453,24 32.212234,24 C32.763015,24 33.212234,23.550781 33.212234,23 C33.212234,22.449219 32.763015,22 32.212234,22 Z M36.212234,22 C35.661453,22 35.212234,22.449219 35.212234,23 C35.212234,23.550781 35.661453,24 36.212234,24 C36.763015,24 37.212234,23.550781 37.212234,23 C37.212234,22.449219 36.763015,22 36.212234,22 Z M40.212234,22 C39.661453,22 39.212234,22.449219 39.212234,23 C39.212234,23.550781 39.661453,24 40.212234,24 C40.763015,24 41.212234,23.550781 41.212234,23 C41.212234,22.449219 40.763015,22 40.212234,22 Z M44.212234,22 C43.661453,22 43.212234,22.449219 43.212234,23 C43.212234,23.550781 43.661453,24 44.212234,24 C44.763015,24 45.212234,23.550781 45.212234,23 C45.212234,22.449219 44.763015,22 44.212234,22 Z M48.212234,22 C47.661453,22 47.212234,22.449219 47.212234,23 C47.212234,23.550781 47.661453,24 48.212234,24 C48.763015,24 49.212234,23.550781 49.212234,23 C49.212234,22.449219 48.763015,22 48.212234,22 Z M52.212234,22 C51.661453,22 51.212234,22.449219 51.212234,23 C51.212234,23.550781 51.661453,24 52.212234,24 C52.763015,24 53.212234,23.550781 53.212234,23 C53.212234,22.449219 52.763015,22 52.212234,22 Z M21.462234,27.96875 C21.419265,27.976563 21.376297,27.988281 21.337234,28 C21.177078,28.027344 21.02864,28.089844 20.899734,28.1875 L15.618484,32.1875 C15.356765,32.375 15.200515,32.679688 15.200515,33 C15.200515,33.320313 15.356765,33.625 15.618484,33.8125 L20.899734,37.8125 C21.348953,38.148438 21.985672,38.058594 22.321609,37.609375 C22.657547,37.160156 22.567703,36.523438 22.118484,36.1875 L19.212234,34 L49.212234,34 L46.305984,36.1875 C45.856765,36.523438 45.766922,37.160156 46.102859,37.609375 C46.438797,38.058594 47.075515,38.148438 47.524734,37.8125 L52.805984,33.8125 C53.067703,33.625 53.223953,33.320313 53.223953,33 C53.223953,32.679688 53.067703,32.375 52.805984,32.1875 L47.524734,28.1875 C47.30989,28.027344 47.040359,27.960938 46.774734,28 C46.743484,28 46.712234,28 46.680984,28 C46.282547,28.074219 45.96614,28.382813 45.884109,28.78125 C45.802078,29.179688 45.970047,29.585938 46.305984,29.8125 L49.212234,32 L19.212234,32 L22.118484,29.8125 C22.520828,29.566406 22.696609,29.070313 22.536453,28.625 C22.380203,28.179688 21.930984,27.90625 21.462234,27.96875 Z M16.212234,42 C15.661453,42 15.212234,42.449219 15.212234,43 C15.212234,43.550781 15.661453,44 16.212234,44 C16.763015,44 17.212234,43.550781 17.212234,43 C17.212234,42.449219 16.763015,42 16.212234,42 Z M20.212234,42 C19.661453,42 19.212234,42.449219 19.212234,43 C19.212234,43.550781 19.661453,44 20.212234,44 C20.763015,44 21.212234,43.550781 21.212234,43 C21.212234,42.449219 20.763015,42 20.212234,42 Z M24.212234,42 C23.661453,42 23.212234,42.449219 23.212234,43 C23.212234,43.550781 23.661453,44 24.212234,44 C24.763015,44 25.212234,43.550781 25.212234,43 C25.212234,42.449219 24.763015,42 24.212234,42 Z M28.212234,42 C27.661453,42 27.212234,42.449219 27.212234,43 C27.212234,43.550781 27.661453,44 28.212234,44 C28.763015,44 29.212234,43.550781 29.212234,43 C29.212234,42.449219 28.763015,42 28.212234,42 Z M32.212234,42 C31.661453,42 31.212234,42.449219 31.212234,43 C31.212234,43.550781 31.661453,44 32.212234,44 C32.763015,44 33.212234,43.550781 33.212234,43 C33.212234,42.449219 32.763015,42 32.212234,42 Z M36.212234,42 C35.661453,42 35.212234,42.449219 35.212234,43 C35.212234,43.550781 35.661453,44 36.212234,44 C36.763015,44 37.212234,43.550781 37.212234,43 C37.212234,42.449219 36.763015,42 36.212234,42 Z M40.212234,42 C39.661453,42 39.212234,42.449219 39.212234,43 C39.212234,43.550781 39.661453,44 40.212234,44 C40.763015,44 41.212234,43.550781 41.212234,43 C41.212234,42.449219 40.763015,42 40.212234,42 Z M44.212234,42 C43.661453,42 43.212234,42.449219 43.212234,43 C43.212234,43.550781 43.661453,44 44.212234,44 C44.763015,44 45.212234,43.550781 45.212234,43 C45.212234,42.449219 44.763015,42 44.212234,42 Z M48.212234,42 C47.661453,42 47.212234,42.449219 47.212234,43 C47.212234,43.550781 47.661453,44 48.212234,44 C48.763015,44 49.212234,43.550781 49.212234,43 C49.212234,42.449219 48.763015,42 48.212234,42 Z M52.212234,42 C51.661453,42 51.212234,42.449219 51.212234,43 C51.212234,43.550781 51.661453,44 52.212234,44 C52.763015,44 53.212234,43.550781 53.212234,43 C53.212234,42.449219 52.763015,42 52.212234,42 Z" id="inside_range" fill="#000000" fill-rule="nonzero"></path>
        </g>
    </g>
</svg>
        </div>
        <div style="font-family: 'IBM Plex Mono', monospace, courier; color: black; font-size: 10px; display: inline-block; vertical-align: middle;">
            <div>col_vals_between()</div>
        </div>
        </td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">sales</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">[0, 10000]</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center"><svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="vertical-align: middle;">
    <g id="unchanged" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="unchanged" transform="translate(0.500000, 0.570147)">
            <rect id="Rectangle" x="0.125132506" y="0" width="23.749735" height="23.7894737"></rect>
            <path d="M5.80375046,8.18194736 C3.77191832,8.18194736 2.11875046,9.83495328 2.11875046,11.8669474 C2.11875046,13.8989414 3.77191832,15.5519474 5.80375046,15.5519474 C7.8355826,15.5519474 9.48875046,13.8989414 9.48875046,11.8669474 C9.48875046,9.83495328 7.83552863,8.18194736 5.80375046,8.18194736 Z M5.80375046,14.814915 C4.17821997,14.814915 2.85578285,13.4924778 2.85578285,11.8669474 C2.85578285,10.2414169 4.17821997,8.91897975 5.80375046,8.91897975 C7.42928095,8.91897975 8.75171807,10.2414169 8.75171807,11.8669474 C8.75171807,13.4924778 7.42928095,14.814915 5.80375046,14.814915 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
            <path d="M13.9638189,8.699335 C13.9364621,8.70430925 13.9091059,8.71176968 13.8842359,8.71923074 C13.7822704,8.73663967 13.6877654,8.77643115 13.6056956,8.83860518 L10.2433156,11.3852598 C10.0766886,11.5046343 9.97720993,11.6986181 9.97720993,11.9025491 C9.97720993,12.1064807 10.0766886,12.3004639 10.2433156,12.4198383 L13.6056956,14.966493 C13.891697,15.1803725 14.2970729,15.1231721 14.5109517,14.8371707 C14.7248313,14.5511692 14.6676309,14.145794 14.3816294,13.9319145 L12.5313257,12.5392127 L21.8812495,12.5392127 L21.8812495,11.2658854 L12.5313257,11.2658854 L14.3816294,9.87318364 C14.6377872,9.71650453 14.7497006,9.40066014 14.6477351,9.11714553 C14.5482564,8.83363156 14.262255,8.65954352 13.9638189,8.699335 Z" id="arrow" fill="#000000" transform="translate(15.929230, 11.894737) rotate(-180.000000) translate(-15.929230, -11.894737) "></path>
        </g>
    </g>
</svg></td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center"><span style="color:#4CA64C;">&check;</span></td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_right">7</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">7<br />1.00</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">0<br />0.00</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px;" class="gt_row gt_center">—</td>
  </tr>
  <tr>
    <td style="height: 40px; background-color: #4CA64C; color: transparent;font-size: 0px;" class="gt_row gt_left">#4CA64C</td>
    <td style="height: 40px; color: #666666;font-size: 13px;font-weight: bold;" class="gt_row gt_right">3</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_left">
        <div style="margin: 0; padding: 0; display: inline-block; height: 30px; vertical-align: middle; width: 16%;">
            <!--?xml version="1.0" encoding="UTF-8"?--><?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 67 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>col_vals_between</title>
    <g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="col_vals_between" transform="translate(0.000000, 0.206897)">
            <path d="M56.712234,1 C59.1975153,1 61.4475153,2.00735931 63.076195,3.63603897 C64.7048747,5.26471863 65.712234,7.51471863 65.712234,10 L65.712234,10 L65.712234,65 L10.712234,65 C8.22695259,65 5.97695259,63.9926407 4.34827294,62.363961 C2.71959328,60.7352814 1.71223397,58.4852814 1.71223397,56 L1.71223397,56 L1.71223397,10 C1.71223397,7.51471863 2.71959328,5.26471863 4.34827294,3.63603897 C5.97695259,2.00735931 8.22695259,1 10.712234,1 L10.712234,1 Z" id="rectangle" stroke="#000000" stroke-width="2" fill="#FFFFFF"></path>
            <path d="M11.993484,21.96875 C10.962234,22.082031 10.188797,22.964844 10.212234,24 L10.212234,42 C10.200515,42.722656 10.579422,43.390625 11.204422,43.753906 C11.825515,44.121094 12.598953,44.121094 13.220047,43.753906 C13.845047,43.390625 14.223953,42.722656 14.212234,42 L14.212234,24 C14.220047,23.457031 14.009109,22.9375 13.626297,22.554688 C13.243484,22.171875 12.723953,21.960938 12.180984,21.96875 C12.118484,21.964844 12.055984,21.964844 11.993484,21.96875 Z M55.993484,21.96875 C54.962234,22.082031 54.188797,22.964844 54.212234,24 L54.212234,42 C54.200515,42.722656 54.579422,43.390625 55.204422,43.753906 C55.825515,44.121094 56.598953,44.121094 57.220047,43.753906 C57.845047,43.390625 58.223953,42.722656 58.212234,42 L58.212234,24 C58.220047,23.457031 58.009109,22.9375 57.626297,22.554688 C57.243484,22.171875 56.723953,21.960938 56.180984,21.96875 C56.118484,21.964844 56.055984,21.964844 55.993484,21.96875 Z M16.212234,22 C15.661453,22 15.212234,22.449219 15.212234,23 C15.212234,23.550781 15.661453,24 16.212234,24 C16.763015,24 17.212234,23.550781 17.212234,23 C17.212234,22.449219 16.763015,22 16.212234,22 Z M20.212234,22 C19.661453,22 19.212234,22.449219 19.212234,23 C19.212234,23.550781 19.661453,24 20.212234,24 C20.763015,24 21.212234,23.550781 21.212234,23 C21.212234,22.449219 20.763015,22 20.212234,22 Z M24.212234,22 C23.661453,22 23.212234,22.449219 23.212234,23 C23.212234,23.550781 23.661453,24 24.212234,24 C24.763015,24 25.212234,23.550781 25.212234,23 C25.212234,22.449219 24.763015,22 24.212234,22 Z M28.212234,22 C27.661453,22 27.212234,22.449219 27.212234,23 C27.212234,23.550781 27.661453,24 28.212234,24 C28.763015,24 29.212234,23.550781 29.212234,23 C29.212234,22.449219 28.763015,22 28.212234,22 Z M32.212234,22 C31.661453,22 31.212234,22.449219 31.212234,23 C31.212234,23.550781 31.661453,24 32.212234,24 C32.763015,24 33.212234,23.550781 33.212234,23 C33.212234,22.449219 32.763015,22 32.212234,22 Z M36.212234,22 C35.661453,22 35.212234,22.449219 35.212234,23 C35.212234,23.550781 35.661453,24 36.212234,24 C36.763015,24 37.212234,23.550781 37.212234,23 C37.212234,22.449219 36.763015,22 36.212234,22 Z M40.212234,22 C39.661453,22 39.212234,22.449219 39.212234,23 C39.212234,23.550781 39.661453,24 40.212234,24 C40.763015,24 41.212234,23.550781 41.212234,23 C41.212234,22.449219 40.763015,22 40.212234,22 Z M44.212234,22 C43.661453,22 43.212234,22.449219 43.212234,23 C43.212234,23.550781 43.661453,24 44.212234,24 C44.763015,24 45.212234,23.550781 45.212234,23 C45.212234,22.449219 44.763015,22 44.212234,22 Z M48.212234,22 C47.661453,22 47.212234,22.449219 47.212234,23 C47.212234,23.550781 47.661453,24 48.212234,24 C48.763015,24 49.212234,23.550781 49.212234,23 C49.212234,22.449219 48.763015,22 48.212234,22 Z M52.212234,22 C51.661453,22 51.212234,22.449219 51.212234,23 C51.212234,23.550781 51.661453,24 52.212234,24 C52.763015,24 53.212234,23.550781 53.212234,23 C53.212234,22.449219 52.763015,22 52.212234,22 Z M21.462234,27.96875 C21.419265,27.976563 21.376297,27.988281 21.337234,28 C21.177078,28.027344 21.02864,28.089844 20.899734,28.1875 L15.618484,32.1875 C15.356765,32.375 15.200515,32.679688 15.200515,33 C15.200515,33.320313 15.356765,33.625 15.618484,33.8125 L20.899734,37.8125 C21.348953,38.148438 21.985672,38.058594 22.321609,37.609375 C22.657547,37.160156 22.567703,36.523438 22.118484,36.1875 L19.212234,34 L49.212234,34 L46.305984,36.1875 C45.856765,36.523438 45.766922,37.160156 46.102859,37.609375 C46.438797,38.058594 47.075515,38.148438 47.524734,37.8125 L52.805984,33.8125 C53.067703,33.625 53.223953,33.320313 53.223953,33 C53.223953,32.679688 53.067703,32.375 52.805984,32.1875 L47.524734,28.1875 C47.30989,28.027344 47.040359,27.960938 46.774734,28 C46.743484,28 46.712234,28 46.680984,28 C46.282547,28.074219 45.96614,28.382813 45.884109,28.78125 C45.802078,29.179688 45.970047,29.585938 46.305984,29.8125 L49.212234,32 L19.212234,32 L22.118484,29.8125 C22.520828,29.566406 22.696609,29.070313 22.536453,28.625 C22.380203,28.179688 21.930984,27.90625 21.462234,27.96875 Z M16.212234,42 C15.661453,42 15.212234,42.449219 15.212234,43 C15.212234,43.550781 15.661453,44 16.212234,44 C16.763015,44 17.212234,43.550781 17.212234,43 C17.212234,42.449219 16.763015,42 16.212234,42 Z M20.212234,42 C19.661453,42 19.212234,42.449219 19.212234,43 C19.212234,43.550781 19.661453,44 20.212234,44 C20.763015,44 21.212234,43.550781 21.212234,43 C21.212234,42.449219 20.763015,42 20.212234,42 Z M24.212234,42 C23.661453,42 23.212234,42.449219 23.212234,43 C23.212234,43.550781 23.661453,44 24.212234,44 C24.763015,44 25.212234,43.550781 25.212234,43 C25.212234,42.449219 24.763015,42 24.212234,42 Z M28.212234,42 C27.661453,42 27.212234,42.449219 27.212234,43 C27.212234,43.550781 27.661453,44 28.212234,44 C28.763015,44 29.212234,43.550781 29.212234,43 C29.212234,42.449219 28.763015,42 28.212234,42 Z M32.212234,42 C31.661453,42 31.212234,42.449219 31.212234,43 C31.212234,43.550781 31.661453,44 32.212234,44 C32.763015,44 33.212234,43.550781 33.212234,43 C33.212234,42.449219 32.763015,42 32.212234,42 Z M36.212234,42 C35.661453,42 35.212234,42.449219 35.212234,43 C35.212234,43.550781 35.661453,44 36.212234,44 C36.763015,44 37.212234,43.550781 37.212234,43 C37.212234,42.449219 36.763015,42 36.212234,42 Z M40.212234,42 C39.661453,42 39.212234,42.449219 39.212234,43 C39.212234,43.550781 39.661453,44 40.212234,44 C40.763015,44 41.212234,43.550781 41.212234,43 C41.212234,42.449219 40.763015,42 40.212234,42 Z M44.212234,42 C43.661453,42 43.212234,42.449219 43.212234,43 C43.212234,43.550781 43.661453,44 44.212234,44 C44.763015,44 45.212234,43.550781 45.212234,43 C45.212234,42.449219 44.763015,42 44.212234,42 Z M48.212234,42 C47.661453,42 47.212234,42.449219 47.212234,43 C47.212234,43.550781 47.661453,44 48.212234,44 C48.763015,44 49.212234,43.550781 49.212234,43 C49.212234,42.449219 48.763015,42 48.212234,42 Z M52.212234,42 C51.661453,42 51.212234,42.449219 51.212234,43 C51.212234,43.550781 51.661453,44 52.212234,44 C52.763015,44 53.212234,43.550781 53.212234,43 C53.212234,42.449219 52.763015,42 52.212234,42 Z" id="inside_range" fill="#000000" fill-rule="nonzero"></path>
        </g>
    </g>
</svg>
        </div>
        <div style="font-family: 'IBM Plex Mono', monospace, courier; color: black; font-size: 10px; display: inline-block; vertical-align: middle;">
            <div>col_vals_between()</div>
        </div>
        </td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">customer_rating</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">[1.0, 5.0]</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center"><svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="vertical-align: middle;">
    <g id="unchanged" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="unchanged" transform="translate(0.500000, 0.570147)">
            <rect id="Rectangle" x="0.125132506" y="0" width="23.749735" height="23.7894737"></rect>
            <path d="M5.80375046,8.18194736 C3.77191832,8.18194736 2.11875046,9.83495328 2.11875046,11.8669474 C2.11875046,13.8989414 3.77191832,15.5519474 5.80375046,15.5519474 C7.8355826,15.5519474 9.48875046,13.8989414 9.48875046,11.8669474 C9.48875046,9.83495328 7.83552863,8.18194736 5.80375046,8.18194736 Z M5.80375046,14.814915 C4.17821997,14.814915 2.85578285,13.4924778 2.85578285,11.8669474 C2.85578285,10.2414169 4.17821997,8.91897975 5.80375046,8.91897975 C7.42928095,8.91897975 8.75171807,10.2414169 8.75171807,11.8669474 C8.75171807,13.4924778 7.42928095,14.814915 5.80375046,14.814915 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
            <path d="M13.9638189,8.699335 C13.9364621,8.70430925 13.9091059,8.71176968 13.8842359,8.71923074 C13.7822704,8.73663967 13.6877654,8.77643115 13.6056956,8.83860518 L10.2433156,11.3852598 C10.0766886,11.5046343 9.97720993,11.6986181 9.97720993,11.9025491 C9.97720993,12.1064807 10.0766886,12.3004639 10.2433156,12.4198383 L13.6056956,14.966493 C13.891697,15.1803725 14.2970729,15.1231721 14.5109517,14.8371707 C14.7248313,14.5511692 14.6676309,14.145794 14.3816294,13.9319145 L12.5313257,12.5392127 L21.8812495,12.5392127 L21.8812495,11.2658854 L12.5313257,11.2658854 L14.3816294,9.87318364 C14.6377872,9.71650453 14.7497006,9.40066014 14.6477351,9.11714553 C14.5482564,8.83363156 14.262255,8.65954352 13.9638189,8.699335 Z" id="arrow" fill="#000000" transform="translate(15.929230, 11.894737) rotate(-180.000000) translate(-15.929230, -11.894737) "></path>
        </g>
    </g>
</svg></td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center"><span style="color:#4CA64C;">&check;</span></td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_right">7</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">7<br />1.00</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">0<br />0.00</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px;" class="gt_row gt_center">—</td>
  </tr>
  <tr>
    <td style="height: 40px; background-color: #4CA64C; color: transparent;font-size: 0px;" class="gt_row gt_left">#4CA64C</td>
    <td style="height: 40px; color: #666666;font-size: 13px;font-weight: bold;" class="gt_row gt_right">4</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_left">
        <div style="margin: 0; padding: 0; display: inline-block; height: 30px; vertical-align: middle; width: 16%;">
            <!--?xml version="1.0" encoding="UTF-8"?--><?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 67 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>col_vals_in_set</title>
    <g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="col_vals_in_set" transform="translate(0.000000, 0.172414)">
            <path d="M56.712234,1 C59.1975153,1 61.4475153,2.00735931 63.076195,3.63603897 C64.7048747,5.26471863 65.712234,7.51471863 65.712234,10 L65.712234,10 L65.712234,65 L10.712234,65 C8.22695259,65 5.97695259,63.9926407 4.34827294,62.363961 C2.71959328,60.7352814 1.71223397,58.4852814 1.71223397,56 L1.71223397,56 L1.71223397,10 C1.71223397,7.51471863 2.71959328,5.26471863 4.34827294,3.63603897 C5.97695259,2.00735931 8.22695259,1 10.712234,1 L10.712234,1 Z" id="rectangle" stroke="#000000" stroke-width="2" fill="#FFFFFF"></path>
            <path d="M44.127969,41.1538382 L31.0814568,41.1538382 C29.9510748,41.1536429 28.8827052,40.9256134 27.9079888,40.5136953 C26.4467442,39.8960136 25.19849,38.8599685 24.3189894,37.5577099 C23.8792391,36.906727 23.5314818,36.1899233 23.2936866,35.4252675 C23.2130217,35.16589 23.1460289,34.9005554 23.0913409,34.6307286 L44.1278714,34.6307286 C45.028466,34.6306309 45.7586488,33.9004481 45.7586488,32.9998535 C45.7586488,32.0992589 45.028466,31.3690761 44.1278714,31.3690761 L23.0905596,31.3690761 C23.1990567,30.8337194 23.3597028,30.3180894 23.5675173,29.8264831 C24.185199,28.3652386 25.2212442,27.1169844 26.5236004,26.2374838 C27.1745833,25.7977334 27.891387,25.4499762 28.6560428,25.2122786 C29.4208939,24.9744833 30.2334994,24.8459665 31.0813591,24.8459665 L44.1277737,24.8459665 C45.0283683,24.8459665 45.7586488,24.1157837 45.7586488,23.2151891 C45.7586488,22.3145945 45.0283683,21.5844117 44.1277737,21.5844117 L31.0813591,21.5844117 C29.5096643,21.5844117 28.0039858,21.9038483 26.6373711,22.4820765 C24.5866678,23.3498583 22.8469049,24.7950871 21.6163267,26.616296 C20.3856508,28.4362354 19.665136,30.6413347 19.6658191,33.0000488 C19.6658191,34.5717436 19.9852563,36.0774222 20.5635822,37.4440369 C21.4312663,39.4947402 22.8765927,41.2345031 24.697704,42.4650813 C26.5176434,43.6957572 28.7227427,44.4155883 31.0814568,44.4155883 L44.1278714,44.4155883 C45.028466,44.4155883 45.7586488,43.6854055 45.7586488,42.7848109 C45.7586488,41.8842163 45.0285636,41.1538382 44.127969,41.1538382 Z" id="set_of" fill="#000000" fill-rule="nonzero"></path>
        </g>
    </g>
</svg>
        </div>
        <div style="font-family: 'IBM Plex Mono', monospace, courier; color: black; font-size: 11px; display: inline-block; vertical-align: middle;">
            <div>col_vals_in_set()</div>
        </div>
        </td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">region</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="gt_row gt_left">North, South, East, West</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center"><svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="vertical-align: middle;">
    <g id="unchanged" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="unchanged" transform="translate(0.500000, 0.570147)">
            <rect id="Rectangle" x="0.125132506" y="0" width="23.749735" height="23.7894737"></rect>
            <path d="M5.80375046,8.18194736 C3.77191832,8.18194736 2.11875046,9.83495328 2.11875046,11.8669474 C2.11875046,13.8989414 3.77191832,15.5519474 5.80375046,15.5519474 C7.8355826,15.5519474 9.48875046,13.8989414 9.48875046,11.8669474 C9.48875046,9.83495328 7.83552863,8.18194736 5.80375046,8.18194736 Z M5.80375046,14.814915 C4.17821997,14.814915 2.85578285,13.4924778 2.85578285,11.8669474 C2.85578285,10.2414169 4.17821997,8.91897975 5.80375046,8.91897975 C7.42928095,8.91897975 8.75171807,10.2414169 8.75171807,11.8669474 C8.75171807,13.4924778 7.42928095,14.814915 5.80375046,14.814915 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
            <path d="M13.9638189,8.699335 C13.9364621,8.70430925 13.9091059,8.71176968 13.8842359,8.71923074 C13.7822704,8.73663967 13.6877654,8.77643115 13.6056956,8.83860518 L10.2433156,11.3852598 C10.0766886,11.5046343 9.97720993,11.6986181 9.97720993,11.9025491 C9.97720993,12.1064807 10.0766886,12.3004639 10.2433156,12.4198383 L13.6056956,14.966493 C13.891697,15.1803725 14.2970729,15.1231721 14.5109517,14.8371707 C14.7248313,14.5511692 14.6676309,14.145794 14.3816294,13.9319145 L12.5313257,12.5392127 L21.8812495,12.5392127 L21.8812495,11.2658854 L12.5313257,11.2658854 L14.3816294,9.87318364 C14.6377872,9.71650453 14.7497006,9.40066014 14.6477351,9.11714553 C14.5482564,8.83363156 14.262255,8.65954352 13.9638189,8.699335 Z" id="arrow" fill="#000000" transform="translate(15.929230, 11.894737) rotate(-180.000000) translate(-15.929230, -11.894737) "></path>
        </g>
    </g>
</svg></td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center"><span style="color:#4CA64C;">&check;</span></td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px;" class="gt_row gt_right">7</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">7<br />1.00</td>
    <td style="height: 40px; color: black;font-family: IBM Plex Mono;font-size: 11px; border-left: 1px dashed #E5E5E5;" class="gt_row gt_right">0<br />0.00</td>
    <td style="height: 40px; background-color: #FCFCFC; border-left: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px; background-color: #FCFCFC; border-right: 1px solid #D3D3D3;" class="gt_row gt_center">&mdash;</td>
    <td style="height: 40px;" class="gt_row gt_center">—</td>
  </tr>
</tbody>
  <tfoot class="gt_sourcenotes">
  <tr>
    <td class="gt_sourcenote" colspan="14" style="text-align: left;"><div style='margin-top: 5px; margin-bottom: 5px;'><span style='background-color: #FFF; color: #444; padding: 0.5em 0.5em; position: inherit; text-transform: uppercase; margin-left: 10px; margin-right: 5px; border: solid 1px #999999; font-variant-numeric: tabular-nums; border-radius: 0; padding: 2px 10px 2px 10px;'>2026-06-04 17:00:11 UTC</span><span style='background-color: #FFF; color: #444; padding: 0.5em 0.5em; position: inherit; margin-right: 5px; border: solid 1px #999999; font-variant-numeric: tabular-nums; border-radius: 0; padding: 2px 10px 2px 10px;'>< 1 s</span><span style='background-color: #FFF; color: #444; padding: 0.5em 0.5em; position: inherit; text-transform: uppercase; margin: 5px 1px 5px -1px; border: solid 1px #999999; font-variant-numeric: tabular-nums; border-radius: 0; padding: 2px 10px 2px 10px;'>2026-06-04 17:00:11 UTC</span></div></td>
  </tr>
</tfoot>
</table>
</div>
<p>In natural language, the steps that the agent performs are:</p>
<ul>
<li>Validate the <code>sales_data</code> DataFrame</li>
<li>Make sure that no values in <code>Date</code> are null</li>
<li>Make sure that the values in <code>sales</code> are between 0 and 10000</li>
<li>Make sure that the values in <code>customer_rating</code> are between 1 and 5</li>
<li>Make sure that the values in <code>region</code> are &ldquo;North&rdquo;, &ldquo;South&rdquo;, &ldquo;East&rdquo;, or &ldquo;West&rdquo;</li>
<li>Now, interrogate!</li>
</ul>
<p>The resulting table lets us know for each step what was expected, how many values passed, the percentage of values that passed, and so on. And, the validation agent works directly with Polars DataFrames, no need to convert to pandas!</p>
<p>Also, about that nifty table that gets output? 👀 <strong>Directly</strong> in this blog post (written in <a href="https://quarto.org/" target="_blank" rel="noopener">Quarto</a>)? <a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">That is Great Tables</a>! But Great Tables isn&rsquo;t just for pointblank&hellip;</p>
<h2 id="creating-beautiful-tables-with-great-tables">Creating beautiful tables with Great Tables
</h2>
<p>Let&rsquo;s summarize some data:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">daily_summary</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">sales_data</span><span class="o">.</span><span class="n">group_by</span><span class="p">(</span><span class="s2">&#34;date&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">agg</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;sales&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;total_sales&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;units_sold&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;total_units&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;customer_rating&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;avg_rating&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="s2">&#34;date&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">regional_summary</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">sales_data</span><span class="o">.</span><span class="n">group_by</span><span class="p">(</span><span class="s2">&#34;region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">agg</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;sales&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;total_sales&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;sales&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;avg_sales&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;units_sold&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;total_units&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;sales&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;sales_trend&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="s2">&#34;total_sales&#34;</span><span class="p">,</span> <span class="n">descending</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>Let&rsquo;s present our regional summary in a ✨publication-quality table✨ using <strong>Great Tables</strong>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_tables</span> <span class="kn">import</span> <span class="n">loc</span><span class="p">,</span> <span class="n">style</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">GT</span><span class="p">(</span><span class="n">regional_summary</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Regional Sales Performance&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">subtitle</span><span class="o">=</span><span class="s2">&#34;Week of January 15-21, 2026&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_currency</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;total_sales&#34;</span><span class="p">,</span> <span class="s2">&#34;avg_sales&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">currency</span><span class="o">=</span><span class="s2">&#34;USD&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="s2">&#34;total_units&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">decimals</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">sep_mark</span><span class="o">=</span><span class="s2">&#34;,&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">fmt_nanoplot</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="s2">&#34;sales_trend&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_type</span><span class="o">=</span><span class="s2">&#34;line&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">autoscale</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">cols_label</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">region</span><span class="o">=</span><span class="s2">&#34;Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">total_sales</span><span class="o">=</span><span class="s2">&#34;Total Sales&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">avg_sales</span><span class="o">=</span><span class="s2">&#34;Average Sale&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">total_units</span><span class="o">=</span><span class="s2">&#34;Units Sold&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">sales_trend</span><span class="o">=</span><span class="s2">&#34;Trend&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">data_color</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="s2">&#34;total_sales&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">palette</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;#f0f0f0&#34;</span><span class="p">,</span> <span class="s2">&#34;#447099&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">domain</span><span class="o">=</span><span class="p">[</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">5000</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_style</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">style</span><span class="o">=</span><span class="n">style</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="n">weight</span><span class="o">=</span><span class="s2">&#34;bold&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">body</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="s2">&#34;region&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_style</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">style</span><span class="o">=</span><span class="n">style</span><span class="o">.</span><span class="n">fill</span><span class="p">(</span><span class="n">color</span><span class="o">=</span><span class="s2">&#34;#e8f4f8&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="n">loc</span><span class="o">.</span><span class="n">body</span><span class="p">(</span><span class="n">rows</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_source_note</span><span class="p">(</span><span class="s2">&#34;Data validated with pointblank · Trend shows daily sales pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">tab_options</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">table_font_size</span><span class="o">=</span><span class="s2">&#34;14px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">heading_title_font_size</span><span class="o">=</span><span class="s2">&#34;18px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">heading_subtitle_font_size</span><span class="o">=</span><span class="s2">&#34;14px&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<div id="ihvfoecgvb" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
#ihvfoecgvb table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }
#ihvfoecgvb thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#ihvfoecgvb p { margin: 0; padding: 0; }
 #ihvfoecgvb .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 14px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #ihvfoecgvb .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #ihvfoecgvb .gt_title { color: #333333; font-size: 18px; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #ihvfoecgvb .gt_subtitle { color: #333333; font-size: 14px; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #ihvfoecgvb .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #ihvfoecgvb .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #ihvfoecgvb .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #ihvfoecgvb .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #ihvfoecgvb .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #ihvfoecgvb .gt_column_spanner_outer:first-child { padding-left: 0; }
 #ihvfoecgvb .gt_column_spanner_outer:last-child { padding-right: 0; }
 #ihvfoecgvb .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #ihvfoecgvb .gt_spanner_row { border-bottom-style: hidden; }
 #ihvfoecgvb .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #ihvfoecgvb .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #ihvfoecgvb .gt_from_md> :first-child { margin-top: 0; }
 #ihvfoecgvb .gt_from_md> :last-child { margin-bottom: 0; }
 #ihvfoecgvb .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #ihvfoecgvb .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #ihvfoecgvb .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #ihvfoecgvb .gt_row_group_first td { border-top-width: 2px; }
 #ihvfoecgvb .gt_row_group_first th { border-top-width: 2px; }
 #ihvfoecgvb .gt_striped { color: #333333; background-color: #F4F4F4; }
 #ihvfoecgvb .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #ihvfoecgvb .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; }
 #ihvfoecgvb .gt_first_grand_summary_row_bottom { border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; }
 #ihvfoecgvb .gt_last_grand_summary_row_top { border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; }
 #ihvfoecgvb .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #ihvfoecgvb .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #ihvfoecgvb .gt_left { text-align: left; }
 #ihvfoecgvb .gt_center { text-align: center; }
 #ihvfoecgvb .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #ihvfoecgvb .gt_font_normal { font-weight: normal; }
 #ihvfoecgvb .gt_font_bold { font-weight: bold; }
 #ihvfoecgvb .gt_font_italic { font-style: italic; }
 #ihvfoecgvb .gt_super { font-size: 65%; }
 #ihvfoecgvb .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #ihvfoecgvb .gt_asterisk { font-size: 100%; vertical-align: 0; }
</style>
<table class="gt_table" data-quarto-postprocess="true" data-quarto-disable-processing="false" data-quarto-bootstrap="false">
<colgroup>
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
</colgroup>
<thead>
<tr class="gt_heading">
<th colspan="5" class="gt_heading gt_title gt_font_normal">Regional Sales Performance</th>
</tr>
<tr class="gt_heading">
<th colspan="5" class="gt_heading gt_subtitle gt_font_normal gt_bottom_border">Week of January 15-21, 2026</th>
</tr>
<tr class="gt_col_headings">
<th id="region" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Region</th>
<th id="total_sales" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Total Sales</th>
<th id="avg_sales" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Average Sale</th>
<th id="total_units" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Units Sold</th>
<th id="sales_trend" class="gt_col_heading gt_columns_bottom_border gt_center" data-quarto-table-cell-role="th" scope="col">Trend</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_left" style="font-weight: bold; background-color: #e8f4f8">North</td>
<td class="gt_row gt_right" style="color: #000000; background-color: #557da2; background-color: #e8f4f8">$4,600.00</td>
<td class="gt_row gt_right" style="background-color: #e8f4f8">$1,533.33</td>
<td class="gt_row gt_right" style="background-color: #e8f4f8">92</td>
<td class="gt_row gt_center" style="background-color: #e8f4f8"><div>
<svg role="img" viewbox="0 0 250 130" style="height: 2em; margin-left: auto; margin-right: auto; font-size: inherit; overflow: visible; vertical-align: middle; position:relative;">
<defs><pattern id="area_pattern" width="8" height="8" patternunits="userSpaceOnUse"><path class="pattern-line" d="M 0,8 l 8,-8 M -1,1 l 4,-4 M 6,10 l 4,-4" stroke="#FF0000" stroke-width="1.5" stroke-linecap="round" shape-rendering="geometricPrecision"></path></pattern></defs><path class="area-closed" d="M 50.0,115.0 125.0,87.72727272727273 200.0,51.36363636363637 200.0,125 50.0,125 Z" stroke="transparent" stroke-width="2" fill="url(#area_pattern)" fill-opacity="0.7"></path><path d="M 50.0,115.0 C 75.0,115.0 100.0,87.72727272727273 125.0,87.72727272727273 C 150.0,87.72727272727273 175.0,51.36363636363637 200.0,51.36363636363637" stroke="#4682B4" stroke-width="8" fill="none"></path><circle cx="50.0" cy="115.0" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><circle cx="125.0" cy="87.72727272727273" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><circle cx="200.0" cy="51.36363636363637" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><g class="y-axis-line"><rect x="0" y="0" width="65" height="130" stroke="transparent" stroke-width="0" fill="transparent"></rect><text x="0" y="19.0" fill="transparent" stroke="transparent" font-size="25">2.30K</text><text x="0" y="126.0" fill="transparent" stroke="transparent" font-size="25">1.20K</text></g><g class="vert-line"><rect x="40.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="60.0" y="20" fill="transparent" stroke="transparent" font-size="30px">1.20K</text></g><g class="vert-line"><rect x="115.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="135.0" y="20" fill="transparent" stroke="transparent" font-size="30px">1.50K</text></g><g class="vert-line"><rect x="190.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="210.0" y="20" fill="transparent" stroke="transparent" font-size="30px">1.90K</text></g>
</svg>
</div></td>
</tr>
<tr>
<td class="gt_row gt_left" style="font-weight: bold">West</td>
<td class="gt_row gt_right" style="color: #000000; background-color: #5e83a6">$4,400.00</td>
<td class="gt_row gt_right">$2,200.00</td>
<td class="gt_row gt_right">88</td>
<td class="gt_row gt_center"><div>
<svg role="img" viewbox="0 0 200 130" style="height: 2em; margin-left: auto; margin-right: auto; font-size: inherit; overflow: visible; vertical-align: middle; position:relative;">
<defs><pattern id="area_pattern" width="8" height="8" patternunits="userSpaceOnUse"><path class="pattern-line" d="M 0,8 l 8,-8 M -1,1 l 4,-4 M 6,10 l 4,-4" stroke="#FF0000" stroke-width="1.5" stroke-linecap="round" shape-rendering="geometricPrecision"></path></pattern></defs><path class="area-closed" d="M 50.0,33.18181818181817 150.0,15.0 150.0,125 50.0,125 Z" stroke="transparent" stroke-width="2" fill="url(#area_pattern)" fill-opacity="0.7"></path><path d="M 50.0,33.18181818181817 C 75.0,33.18181818181817 125.0,15.0 150.0,15.0" stroke="#4682B4" stroke-width="8" fill="none"></path><circle cx="50.0" cy="33.18181818181817" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><circle cx="150.0" cy="15.0" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><g class="y-axis-line"><rect x="0" y="0" width="65" height="130" stroke="transparent" stroke-width="0" fill="transparent"></rect><text x="0" y="19.0" fill="transparent" stroke="transparent" font-size="25">2.30K</text><text x="0" y="126.0" fill="transparent" stroke="transparent" font-size="25">1.20K</text></g><g class="vert-line"><rect x="40.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="60.0" y="20" fill="transparent" stroke="transparent" font-size="30px">2.10K</text></g><g class="vert-line"><rect x="140.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="160.0" y="20" fill="transparent" stroke="transparent" font-size="30px">2.30K</text></g>
</svg>
</div></td>
</tr>
<tr>
<td class="gt_row gt_left" style="font-weight: bold">South</td>
<td class="gt_row gt_right" style="color: #000000; background-color: #87a2bb">$3,450.00</td>
<td class="gt_row gt_right">$1,725.00</td>
<td class="gt_row gt_right">69</td>
<td class="gt_row gt_center"><div>
<svg role="img" viewbox="0 0 200 130" style="height: 2em; margin-left: auto; margin-right: auto; font-size: inherit; overflow: visible; vertical-align: middle; position:relative;">
<defs><pattern id="area_pattern" width="8" height="8" patternunits="userSpaceOnUse"><path class="pattern-line" d="M 0,8 l 8,-8 M -1,1 l 4,-4 M 6,10 l 4,-4" stroke="#FF0000" stroke-width="1.5" stroke-linecap="round" shape-rendering="geometricPrecision"></path></pattern></defs><path class="area-closed" d="M 50.0,60.45454545454546 150.0,74.09090909090908 150.0,125 50.0,125 Z" stroke="transparent" stroke-width="2" fill="url(#area_pattern)" fill-opacity="0.7"></path><path d="M 50.0,60.45454545454546 C 75.0,60.45454545454546 125.0,74.09090909090908 150.0,74.09090909090908" stroke="#4682B4" stroke-width="8" fill="none"></path><circle cx="50.0" cy="60.45454545454546" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><circle cx="150.0" cy="74.09090909090908" r="10" stroke="#FFFFFF" stroke-width="4" fill="#FF0000"></circle><g class="y-axis-line"><rect x="0" y="0" width="65" height="130" stroke="transparent" stroke-width="0" fill="transparent"></rect><text x="0" y="19.0" fill="transparent" stroke="transparent" font-size="25">2.30K</text><text x="0" y="126.0" fill="transparent" stroke="transparent" font-size="25">1.20K</text></g><g class="vert-line"><rect x="40.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="60.0" y="20" fill="transparent" stroke="transparent" font-size="30px">1.80K</text></g><g class="vert-line"><rect x="140.0" y="0" width="20" height="130" stroke="transparent" stroke-width="12" fill="transparent"></rect><text x="160.0" y="20" fill="transparent" stroke="transparent" font-size="30px">1.65K</text></g>
</svg>
</div></td>
</tr>
</tbody><tfoot class="gt_sourcenotes">
<tr>
<td colspan="5" class="gt_sourcenote">Data validated with pointblank · Trend shows daily sales pattern</td>
</tr>
</tfoot>
&#10;</table>
</div>
<p>In this example, Great Tables:</p>
<ul>
<li>Adds a title using <code>tab_header</code></li>
<li>Formats currency using <code>fmt_currency</code></li>
<li>Formats numbers using <code>fmt_number</code></li>
<li>Creates a nanoplot (a miniature line chart) using <code>fmt_nanoplot</code></li>
<li>Labels the columns using <code>cols_label</code></li>
<li>Edit color, styling, and font sizes with <code>data_color</code>, <code>tab_style</code>, and <code>tab_options</code></li>
<li>Add a source note using <code>tab_source_note</code></li>
</ul>
<p>All with full Polars support! In fact, Great Tables is the <a href="https://docs.pola.rs/user-guide/misc/styling/" target="_blank" rel="noopener">default way to style Polars DataFrames</a>, using <code>df.style</code>.</p>
<h2 id="visualizations-with-plotnine">Visualizations with plotnine
</h2>
<p>For visualizations, <a href="https://plotnine.org/" target="_blank" rel="noopener"><strong>plotnine</strong></a> brings the <a href="https://en.wikipedia.org/wiki/Wilkinson%27s_Grammar_of_Graphics" target="_blank" rel="noopener">grammar of graphics</a> to Python. Let&rsquo;s create some nice-looking plots:</p>
<h3 id="daily-sales-trend">Daily sales trend
</h3>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ggplot</span><span class="p">(</span><span class="n">daily_summary</span><span class="p">,</span> <span class="n">aes</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s2">&#34;date&#34;</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="s2">&#34;total_sales&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">geom_line</span><span class="p">(</span><span class="n">color</span><span class="o">=</span><span class="s2">&#34;#447099&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mf">1.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">geom_point</span><span class="p">(</span><span class="n">color</span><span class="o">=</span><span class="s2">&#34;#447099&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="s2">&#34;white&#34;</span><span class="p">,</span> <span class="n">stroke</span><span class="o">=</span><span class="mf">1.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">geom_text</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">aes</span><span class="p">(</span><span class="n">label</span><span class="o">=</span><span class="s2">&#34;total_sales&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">va</span><span class="o">=</span><span class="s2">&#34;bottom&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">nudge_y</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">size</span><span class="o">=</span><span class="mi">9</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#333333&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">format_string</span><span class="o">=</span><span class="s2">&#34;$</span><span class="si">{:,.0f}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">scale_y_continuous</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">labels</span><span class="o">=</span><span class="k">lambda</span> <span class="n">l</span><span class="p">:</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;$</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="s2">,.0f</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">l</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">limits</span><span class="o">=</span><span class="p">(</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">2600</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">expand</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">labs</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Daily Sales Trend&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">subtitle</span><span class="o">=</span><span class="s2">&#34;Week of January 15-21, 2026 • Total revenue trending upward&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">y</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">theme_minimal</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">theme</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_title</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">16</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;bold&#34;</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#2c3e50&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_subtitle</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">11</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#7f8c8d&#34;</span><span class="p">,</span> <span class="n">margin</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">15</span><span class="p">}),</span>
</span></span><span class="line"><span class="cl">        <span class="n">axis_title_y</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">axis_text_y</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#666666&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">axis_text_x</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#666666&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_grid_major_x</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_grid_minor</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_grid_major_y</span><span class="o">=</span><span class="n">element_line</span><span class="p">(</span><span class="n">color</span><span class="o">=</span><span class="s2">&#34;#e0e0e0&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mf">0.5</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_background</span><span class="o">=</span><span class="n">element_rect</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="s2">&#34;white&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_background</span><span class="o">=</span><span class="n">element_rect</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="s2">&#34;white&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">figure_size</span><span class="o">=</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-06-04_libraries-for-python-polars/index_files/figure-markdown_strict/cell-9-output-1.png" data-fig-alt="Line chart showing daily sales from January 15-21, 2026. Sales increase from around $1,900 to $2,300 over the week, with values labeled on each data point." width="768" height="576" />
<h3 id="sales-by-region-and-product">Sales by region and product
</h3>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Custom color palette with Posit-inspired colors</span>
</span></span><span class="line"><span class="cl"><span class="n">product_colors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Widget A&#34;</span><span class="p">:</span> <span class="s2">&#34;#447099&#34;</span><span class="p">,</span>  <span class="c1"># Blue</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Widget B&#34;</span><span class="p">:</span> <span class="s2">&#34;#72994e&#34;</span><span class="p">,</span>  <span class="c1"># Green</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Widget C&#34;</span><span class="p">:</span> <span class="s2">&#34;#c65d47&#34;</span><span class="p">,</span>  <span class="c1"># Rust</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ggplot</span><span class="p">(</span><span class="n">sales_data</span><span class="p">,</span> <span class="n">aes</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s2">&#34;region&#34;</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="s2">&#34;sales&#34;</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="s2">&#34;product&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">geom_col</span><span class="p">(</span><span class="n">position</span><span class="o">=</span><span class="n">position_dodge</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="mf">0.8</span><span class="p">),</span> <span class="n">width</span><span class="o">=</span><span class="mf">0.7</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">scale_fill_manual</span><span class="p">(</span><span class="n">values</span><span class="o">=</span><span class="n">product_colors</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">scale_y_continuous</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">labels</span><span class="o">=</span><span class="k">lambda</span> <span class="n">l</span><span class="p">:</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;$</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="s2">,.0f</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">l</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">limits</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2500</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">breaks</span><span class="o">=</span><span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2501</span><span class="p">,</span> <span class="mi">500</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">expand</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">labs</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Sales Performance by Region&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">subtitle</span><span class="o">=</span><span class="s2">&#34;Product comparison across geographic markets&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">y</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">theme_minimal</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">+</span> <span class="n">theme</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_title</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">16</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;bold&#34;</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#2c3e50&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_subtitle</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">11</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#7f8c8d&#34;</span><span class="p">,</span> <span class="n">margin</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">15</span><span class="p">}),</span>
</span></span><span class="line"><span class="cl">        <span class="n">axis_title</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">axis_text_y</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#666666&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">axis_text_x</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">11</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;#333333&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;bold&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">legend_position</span><span class="o">=</span><span class="s2">&#34;top&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">legend_title</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">legend_text</span><span class="o">=</span><span class="n">element_text</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">10</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">legend_box_margin</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_grid_major_x</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_grid_minor</span><span class="o">=</span><span class="n">element_blank</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_grid_major_y</span><span class="o">=</span><span class="n">element_line</span><span class="p">(</span><span class="n">color</span><span class="o">=</span><span class="s2">&#34;#e0e0e0&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mf">0.5</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot_background</span><span class="o">=</span><span class="n">element_rect</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="s2">&#34;white&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">panel_background</span><span class="o">=</span><span class="n">element_rect</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="s2">&#34;white&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">figure_size</span><span class="o">=</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-06-04_libraries-for-python-polars/index_files/figure-markdown_strict/cell-10-output-1.png" data-fig-alt="Grouped bar chart showing sales performance by region (North, South, West) and product (Widget A, B, C). Bars are color-coded by product with Widget A in blue, Widget B in green, and Widget C in rust. West region shows highest sales, particularly for Widget C at approximately $2,300." width="768" height="576" />
<p>Plotnine works seamlessly with Polars DataFrames, no conversion needed! These visualizations can include:</p>
<ul>
<li>Values displayed directly on points for easy reading with <code>geom_text</code></li>
<li>Currency labels, appropriate limits, and controlled breaks with <code>scale_y_continuous</code></li>
<li>Larger, bolder titles with subtle subtitle styling with <code>theme</code></li>
<li>Pure white backgrounds with subtle gray gridlines with <code>theme_minimal</code> (my favorite built-in theme)</li>
</ul>
<p>Again, with full Polars support. There&rsquo;s more about it in the <a href="https://docs.pola.rs/user-guide/misc/visualization/" target="_blank" rel="noopener">Polars documentation for visualization</a>.</p>
<h2 id="ai-powered-insights-with-mall">AI-powered insights with mall
</h2>
<p>Finally, let&rsquo;s use <a href="https://mlverse.github.io/mall/" target="_blank" rel="noopener"><strong>mall</strong></a> to add LLM-powered analysis to our workflow. I used <a href="https://ollama.com/" target="_blank" rel="noopener">Ollama</a> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> with a local model, but mall works with OpenAI, Anthropic, and other providers through the <a href="https://github.com/cpsievert/chatlas" target="_blank" rel="noopener">chatlas</a> package.</p>
<p>Mall extends Polars DataFrames with an <code>.llm</code> accessor that provides natural language operations. We can use mall to add natural language descriptions to our sales data, rating the performance of each row as &ldquo;low&rdquo;, &ldquo;medium&rdquo;, or &ldquo;high&rdquo;:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">sales_data</span><span class="o">.</span><span class="n">llm</span><span class="o">.</span><span class="n">use</span><span class="p">(</span><span class="s2">&#34;ollama&#34;</span><span class="p">,</span> <span class="s2">&#34;llama3.2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sales_with_performance</span> <span class="o">=</span> <span class="n">sales_data</span><span class="o">.</span><span class="n">llm</span><span class="o">.</span><span class="n">classify</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;sales&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;high&#34;</span><span class="p">,</span> <span class="s2">&#34;medium&#34;</span><span class="p">,</span> <span class="s2">&#34;low&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">pred_name</span><span class="o">=</span><span class="s2">&#34;performance&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sales_with_performance</span><span class="o">.</span><span class="n">select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">,</span> <span class="s2">&#34;region&#34;</span><span class="p">,</span> <span class="s2">&#34;product&#34;</span><span class="p">,</span> <span class="s2">&#34;sales&#34;</span><span class="p">,</span> <span class="s2">&#34;performance&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">))</span></span></span></code></pre></div></div>
<pre><code>shape: (7, 5)
┌────────────┬────────┬──────────┬───────┬─────────────┐
│ date       ┆ region ┆ product  ┆ sales ┆ performance │
│ ---        ┆ ---    ┆ ---      ┆ ---   ┆ ---         │
│ date       ┆ str    ┆ str      ┆ i64   ┆ str         │
╞════════════╪════════╪══════════╪═══════╪═════════════╡
│ 2026-01-15 ┆ North  ┆ Widget A ┆ 1200  ┆ low         │
│ 2026-01-16 ┆ South  ┆ Widget B ┆ 1800  ┆ low         │
│ 2026-01-17 ┆ North  ┆ Widget A ┆ 1500  ┆ low         │
│ 2026-01-18 ┆ West   ┆ Widget C ┆ 2100  ┆ low         │
│ 2026-01-19 ┆ South  ┆ Widget B ┆ 1650  ┆ low         │
│ 2026-01-20 ┆ North  ┆ Widget A ┆ 1900  ┆ low         │
│ 2026-01-21 ┆ West   ┆ Widget C ┆ 2300  ┆ low         │
└────────────┴────────┴──────────┴───────┴─────────────┘
</code></pre>
<p>Or we can generate custom descriptions for each product:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># mall&#39;s custom() processes a single text column, so first combine the</span>
</span></span><span class="line"><span class="cl"><span class="c1"># relevant fields into one column for the LLM to read.</span>
</span></span><span class="line"><span class="cl"><span class="n">sales_with_description</span> <span class="o">=</span> <span class="n">sales_data</span><span class="o">.</span><span class="n">with_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">pl</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Product: </span><span class="si">{}</span><span class="s2">, Region: </span><span class="si">{}</span><span class="s2">, Sales: </span><span class="si">{}</span><span class="s2">, Customer rating: </span><span class="si">{}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;product&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;region&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;sales&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">pl</span><span class="o">.</span><span class="n">col</span><span class="p">(</span><span class="s2">&#34;customer_rating&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span><span class="o">.</span><span class="n">alias</span><span class="p">(</span><span class="s2">&#34;product_info&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="o">.</span><span class="n">llm</span><span class="o">.</span><span class="n">custom</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;product_info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">pred_name</span><span class="o">=</span><span class="s2">&#34;description&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">prompt</span><span class="o">=</span><span class="s2">&#34;Based on this product, region, sales amount, and customer rating, create a brief insight about the product&#39;s performance in 15 words or less&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">pl</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">fmt_str_lengths</span><span class="o">=</span><span class="mi">200</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">sales_with_description</span><span class="o">.</span><span class="n">select</span><span class="p">([</span><span class="s2">&#34;product&#34;</span><span class="p">,</span> <span class="s2">&#34;description&#34;</span><span class="p">]))</span></span></span></code></pre></div></div>
<pre><code>shape: (7, 2)
┌──────────┬───────────────────────────────────────────────────────────────────────────────────────┐
│ product  ┆ description                                                                           │
│ ---      ┆ ---                                                                                   │
│ str      ┆ str                                                                                   │
╞══════════╪═══════════════════════════════════════════════════════════════════════════════════════╡
│ Widget A ┆ Widget A performs well in North region with strong customer satisfaction and moderate │
│          ┆ sales growth.                                                                         │
│ Widget B ┆ Widget B performs well in the South region with high customer satisfaction and sales. │
│ Widget A ┆ Widget A performs well in the North region with high sales and customer satisfaction  │
│          ┆ ratings.                                                                              │
│ Widget C ┆ Widget C excels with high sales and exceptional customer satisfaction in Western      │
│          ┆ region markets.                                                                       │
│ Widget B ┆ Widget B performs well with strong sales and high customer satisfaction ratings       │
│          ┆ overall.                                                                              │
│ Widget A ┆ Widget A excels with high sales (1900) and excellent customer reviews (4.8/5 in North │
│          ┆ region).                                                                              │
│ Widget C ┆ Widget C performs well with high sales and exceptional customer satisfaction in       │
│          ┆ Western region.                                                                       │
└──────────┴───────────────────────────────────────────────────────────────────────────────────────┘
</code></pre>
<p>Mall has a bunch of other powerful operations you can use:</p>
<ul>
<li><code>.llm.classify</code> &mdash; Categorize data into predefined labels</li>
<li><code>.llm.sentiment</code> &mdash; Analyze sentiment (positive/negative/neutral)</li>
<li><code>.llm.summarize</code> &mdash; Condense text columns to key points</li>
<li><code>.llm.extract</code> &mdash; Pull specific information from text</li>
<li><code>.llm.translate</code> &mdash; Convert text to another language</li>
<li><code>.llm.verify</code> &mdash; Check if statements are supported by data</li>
</ul>
<p>And not surprisingly, mall keeps everything in Polars format, which means fast, AI-enhanced data operations that fit naturally into your Polars pipelines.</p>
<h2 id="wrapping-up">Wrapping up
</h2>
<p>The Python data ecosystem has embraced Polars, and so has Posit! These four libraries show how we can build complete data workflows without ever leaving the Polars DataFrame format:</p>
<ul>
<li><strong>pointblank</strong> &mdash; Ensure your data quality before analysis begins</li>
<li><strong>Great Tables</strong> &mdash; Create publication-ready tables with rich formatting options</li>
<li><strong>plotnine</strong> &mdash; Build beautiful, reproducible visualizations with the grammar of graphics</li>
<li><strong>mall</strong> &mdash; Integrate LLM capabilities directly into your data pipelines</li>
</ul>
<p>All of these libraries work seamlessly with Polars, so you can stay in the fast, efficient world of Polars from start to finish. Hope you check them out!</p>
<h2 id="learn-more">Learn more
</h2>
<ul>
<li><a href="https://posit-dev.github.io/pointblank/" target="_blank" rel="noopener">pointblank documentation</a></li>
<li><a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">Great Tables documentation</a></li>
<li><a href="https://plotnine.org/" target="_blank" rel="noopener">plotnine documentation</a></li>
<li><a href="https://mlverse.github.io/mall/" target="_blank" rel="noopener">mall documentation</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>For instructions, please review the <a href="https://posit.co/blog/setting-up-local-llms-for-r-and-python" target="_blank" rel="noopener">Setting up local LLMs for R and Python</a> blog post.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-04_libraries-for-python-polars/images/featured-image.jpg" length="77502" type="image/jpeg" />
    </item>
  </channel>
</rss>
