Scripts

Python and R on the canvas — read your tables as data frames, compute anything, and emit new tables and tiles.

A script is a Python or R program that lives on the canvas: it reads the document's tables and tiles, computes whatever the language can compute, and emits new tables and tiles as canvas objects. Scripts never run automatically — you click Run.

A script's language is chosen when you create it and doesn't change afterwards. Both languages get the same editor, the same lineage arrows, the same refresh behaviour, and the same runtime API — spelled in each language's own idiom.

Requirements

Scripts use your own interpreter — nothing is bundled:

LanguageNeedsFound as
Pythonpandas and duckdbpython, py, python3
Python, for statistical testsalso scipy and statsmodelsas above
Rduckdb and jsonlite (with DBI)Rscript

A language with no usable interpreter keeps its entry points hidden — if you have Python but not R, you'll simply never see R. Point DreamSheets at a specific interpreter (a virtualenv, a non-PATH R) in the Scripts section of AI Settings. Each interpreter stays warm between runs, so repeated runs don't pay the import tax.

Writing a script

Right-click empty canvas and pick Add Python script here… or Add R script here… — only the languages you have installed are offered. Insert → Python Script… / R Script… does the same thing. Either opens the script editor: a syntax-highlighted source editor, a built-in API cheat-sheet, an optional Suggest with AI helper that drafts the script from a description, and a Run button.

The script environment, in both languages:

PythonRWhat it is
sheet.MyTablesheet$MyTableThe table named MyTable
sheet["My Table"]sheet[["My Table"]]The same, for names with spaces
.to_df()(not needed)Python's handle is lazy; R hands you the data frame directly
sheet.sql("SELECT …")sheet$sql("SELECT …")Run DuckDB SQL across the document's tables
tiles["My Tile"]tiles[["My Tile"]]A tile's value
emit_table(df, name="…")emit_table(df, name = "…")Create/update a table on the canvas
emit_tile(value, name="…")emit_tile(value, name = "…")Create/update a tile
pd, duckdb pre-importedbase R attached
orders = sheet.Orders.to_df()
by_month = (orders
    .assign(month=pd.to_datetime(orders["Date"]).dt.to_period("M").astype(str))
    .groupby("month", as_index=False)["Amount"].sum())

emit_table(by_month, name="MonthlyRevenue")
emit_tile(orders["Amount"].sum(), name="TotalRevenue")
orders <- sheet$Orders
orders$month <- format(as.Date(orders$Date), "%Y-%m")
by_month <- aggregate(Amount ~ month, data = orders, FUN = sum)

emit_table(by_month, name = "MonthlyRevenue")
emit_tile(sum(orders$Amount), name = "TotalRevenue")

If a script ends with a bare result value and emits nothing explicitly, DreamSheets emits it for you — a data frame becomes a table, anything else becomes a tile.

For large tables, prefer sheet.sql(...) / sheet$sql(...) — the query is pushed into DuckDB over the table's storage instead of materializing everything into memory first. This matters more in R, where sheet$MyTable loads the whole table.

Python extras

Python's table handle is lazy, so it also offers .batches(n=100_000) to iterate the table in Arrow record batches (out-of-core for huge tables) and .schema for the table's schema as a DataFrame. Python scripts also get IDE-style autocomplete in the editor; R's editor is plain for now.

R notes

R scripts auto-print visible top-level results, like the R console — a bare head(df) shows its output. Base R is attached; anything else (dplyr, tidyr, a modelling package) must be installed in the R that DreamSheets is using.

Refresh, lineage, and iteration

  • Outputs are keyed by the script's own emit names: re-running the script updates the tables and tiles it created in place rather than creating duplicates. The match is on the script's recorded outputs, never on a document-wide name — so a script's first run that emits name="Sales" into a document that already has a Sales table creates Sales (2) rather than overwriting someone else's table. Script-produced cards get Refresh script / Edit script… in their menus, and a refresh always re-runs in the script's own language.
  • Column types follow the data frame, via DuckDB: integer dtypes land as integer columns, floats as numbers, datetime.date values (e.g. df["d"].dt.date) as dates, datetime64 as timestamps, strings as text. Convert before emitting if you want a date rather than a timestamp.
  • DreamSheets records which tables and tiles a run read, and draws lineage arrows from the sources into the script card and out to its outputs.
  • Errors show only your script's lines — library and runtime internals are stripped, so the message always points at something you wrote. print() / cat() output is captured and shown.

Trust and safety

Scripts run with your interpreter's full capabilities (files, network — whatever your Python or R can do). That's the point — and the risk. So:

  • Scripts never auto-run, ever. Opening a document executes nothing.
  • The first time you run a script from a document you loaded, DreamSheets asks whether you trust this document's scripts; the answer resets when you open a different document.
  • Scripts read the open document's tables and tile values only, and write back only through emit_table / emit_tile — each run's output is a normal undoable change.
  • A runaway script is stopped after two minutes rather than wedging the app.

Scripts vs. the alternatives

  • Reshaping data with joins and grouping → a query table is simpler and refreshable in place.
  • A derived value per row → a calculated column.
  • Iterative algorithms, calling libraries (stats, ML, requests), custom cleanup, simulation → a script. Reach for R when the analysis you want is a CRAN package; reach for Python when it's a pandas/scikit one.