Framework
Sign in Sign up

Structured data science workflows

Framework helps you set up a data science project, keep track of the data and settings you use, and share your results. Start a project in Framework Cloud, or with our R, Python, or command-line tools.

Load your project and publish a model
library(framework)scaffold() # Read cataloged datapenguins <- data_read("inputs.raw.penguins") # Fit a modelmodel <- lm(body_mass_g ~ flipper_length_mm + species,            data = penguins) # Publish itpublish(model, slug = "penguin-body-mass")
R and Python examples

Scaffolding and analysis example

Four ways to start a Framework project

Create a project with the R or Python package, or from the command line. Framework Cloud can also put the project on GitHub or give you a ZIP, with no package needed.

Use a package

Create a project from R or Python.

framework::new_project(...)

Use the command line

Create a project from your terminal with the Framework CLI.

fw new penguin-study

Start from GitHub

Start with a blueprint on GitHub and clone the repository.

username/penguin-study git clone https://github.com/username/penguin-study.git

Download a ZIP

Download the project files and unpack them on your computer.

Framework works with R, Python, Stata, and the command line

The same analysis in each language. Read the penguins dataset from the project's data catalog, summarize it, fit a model, and publish each result to its own link.

scripts/analysis.R

library(framework)scaffold() # Read a dataset from the project catalogpenguins <- data_read("inputs.raw.penguins")penguins # Summarize, fit, and plotby_species <- penguins |>  group_by(species) |>  summarize(n = n(), mean_mass_g = mean(body_mass_g, na.rm = TRUE))by_species model <- lm(body_mass_g ~ flipper_length_mm + species, data = penguins)summary(model) plot <- ggplot(penguins, aes(flipper_length_mm, body_mass_g)) +  geom_point(aes(color = species)) # Publish each onepublish(penguins, slug = "penguins")publish(by_species, slug = "mass-by-species")publish(model, slug = "penguin-body-mass")publish(plot, slug = "flipper-and-mass")
Data penguins
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_length_mm
   <chr>   <chr>              <dbl>         <dbl>             <dbl>
 1 Adelie  Torgersen           39.1          18.7               181
 2 Adelie  Torgersen           39.5          17.4               186
 3 Adelie  Torgersen           40.3          18                 195
 4 Adelie  Torgersen           NA            NA                  NA
…
Model summary(model)
Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       -4031.477    584.151  -6.901 2.55e-11 ***
flipper_length_mm    40.705      3.071  13.255  < 2e-16 ***
speciesChinstrap   -206.510     57.731  -3.577 0.000398 ***
speciesGentoo       266.810     95.264   2.801 0.005392 ** 
…
Multiple R-squared:  0.7826,	Adjusted R-squared:  0.7807 

by_species

# A tibble: 3 × 3
  species       n mean_mass_g
  <chr>     <int>       <dbl>
1 Adelie      152       3701.
2 Chinstrap    68       3733.
3 Gentoo      124       5076.
Scatterplot of body mass against flipper length for 342 penguins, colored by species

Connect to Framework Cloud to reuse project settings and publish updates at the same link.

Framework keeps your project structured from start to finish

Start the project, run the analysis, and publish the results with one folder layout, one data catalog, and one set of settings.

01

Start your project

Build a project from trusted blueprints. Catalog your data, manage package dependencies, set safety guidelines, configure Quarto or Jupyter notebooks, and set up database connections in one place. Signed in to Framework Cloud, a new project starts from your saved blueprint.

R console
framework::new_project("penguin-study") # Signed in to Framework Cloud, the project starts# from your saved blueprint: its folders, package# list, and notebook settings. Open the files to see them. # Or use a project created in Cloudframework::new_project(key = "<project token>")

02

Conduct your analysis

Load your project settings and helper functions, read data by its catalog name, and check whether an input has changed.

scripts/analysis.R
library(framework) # Load all files in functions/.# Apply the configured ggplot theme.scaffold() # Read the input and record its hashpenguins <- data_read("inputs.raw.penguins")penguins <- drop_incomplete(penguins)model <- lm(body_mass_g ~ flipper_length_mm + species, penguins)plot <- ggplot(penguins, aes(flipper_length_mm, body_mass_g)) +  geom_point() # Verify that the input is unchangeddata_verify("inputs.raw.penguins")

functions/data_prep.R

# Sourced by scaffold()drop_incomplete <- function(data) {  # Keep penguins with every measurement  data[complete.cases(data), ]}

03

Publish your work

Publish a model from your script and share its link. Publish again with the same slug and collaborators get the latest version at that link.

scripts/analysis.R
publish(model, slug = "penguin-body-mass")
Published version 2: www.framework.pub/@examples/penguin-study/penguin-body-mass

Penguin body mass model Version 2

R · lm() · body_mass_g ~ flipper_length_mm + species

Term Estimate Std. error p-value
(Intercept) −4031.477 584.151 <0.001
flipper_length_mm 40.705 3.071 <0.001
speciesChinstrap −206.510 57.731 <0.001
speciesGentoo 266.810 95.264 0.005

Framework Cloud

Framework Cloud is a companion web app for Framework. It stores your project defaults, blueprints, AI skills, and other settings. It also lets you share your work securely.

Use it for free at framework.pub or host it on your own server.

Language default for new projects: R, Python, or Stata

Choose R, Python, or Stata for new projects.

Data security and integrity

Framework keeps private data out of Git, guards inputs against accidental changes, and records fingerprints you can show collaborators and reviewers.

Explicit private directories

Keep sensitive inputs and unreviewed outputs in private/ directories. Put reviewed results in public/ directories when they are ready to share.

Git safeguards

Each structure has an editable .gitignore template. Optional pre-commit checks catch private data and missing ignore rules before a commit.

File fingerprints

data_read() records input hashes and warns when data changes. Lock inputs to stop changed files from being read, and check them with data_verify().

Data verification ledger

Share input fingerprints and verification metadata through Framework Cloud for transparency. Your datasets stay on your machine.

Terminal
$ git add inputs/private/raw/penguins.csv$ git commit -m "Add study data" ✗ FAIL: gitignore coverage (2 issues)✗ FAIL: private data exposure (1 issue)...  inputs/private/raw/penguins.csv  Reason: Private data file not in .gitignore...✗ AUDIT FAILED - Critical security issues found✗ Data security check failed 

Output excerpt from a synthetic example. The commit was blocked. Local Git hooks can be bypassed.

AI agent skills

Framework provides skills for R, Python, and Stata, including plotting skills for ggplot2, matplotlib, and Stata graphs. Create your own and share them across projects.

.claude/skills/ggplot2-exploring/SKILL.md
---name: ggplot2-exploringdescription: Use when plotting data in R to understand it - checking distributions, comparing groups, looking for relationships, trends, or problems in a dataset. Covers choosing the chart that answers the question and quick, accurate ggplot2 figures. For figures meant for a paper, report, or slides, use ggplot2-publishing instead.metadata:  title: Exploring data with ggplot2  summary: Quick, accurate ggplot2 figures for understanding data.--- # Exploring data with ggplot2 Exploratory figures are for the analyst and the team. Their job is to show thedata accurately so you can understand it. Make them quickly, make many, and donot polish them. ## Rules - Set the theme once, right after the setup code (after `scaffold()` in a  Framework project), with `theme_set(theme_minimal())`. If the project defines  its own theme, such as a `theme_*()` function in `functions/`, set that  instead. Never leave ggplot2's default gray background.- Compute the counts and summaries a figure needs in memory. Do not write a new  dataset to `inputs/` or anywhere else just to draw figures. ## Choose the chart from the question Decide what question the figure answers, then use the chart that answers it.Position on a common scale is the most accurate way to compare values, soprefer charts that put the values being compared on one shared axis. | Question | Chart | ggplot2 ||---|---|---|| How is one numeric variable distributed? | Histogram | `geom_histogram()` || How does a numeric variable differ across groups? | Box plot, or violin plot with the points | `geom_boxplot()`, `geom_violin()`, `geom_jitter()` || How many are in each category? | Bar chart, sorted | `geom_col()` or `geom_bar()` || How do two numeric variables relate? | Scatterplot | `geom_point()`, optionally `geom_smooth()` || How does something change over time or another ordered variable? | Line chart | `geom_line()` with `geom_point()` || How do two estimates compare across many groups? | Dot plot | `geom_point()` with `geom_segment()` || Does a pattern differ across subgroups? | Small multiples | `facet_wrap()` or `facet_grid()` | Avoid pie charts, stacked bars for comparing anything but the bottom segment,dual y axes, and 3-D effects. They make comparisons harder to read. ## Make it accurate - **Histograms:** try more than one bin width. The default of 30 bins can hide  or invent features. Set `binwidth` in the units of the variable.- **Group summaries:** a box plot hides how many observations each group has  and whether the distribution has more than one peak. With a few hundred  points or fewer, add the data with `geom_jitter(width = 0.2, alpha = 0.3)`.- **Overplotting:** with many points, lower `alpha`, use smaller points, or use  `geom_bin2d()` or `geom_hex()`.- **Skewed variables:** use `scale_x_log10()` or `scale_y_log10()` for strongly  right-skewed values such as income or counts. Say so in the axis label.- **Bar charts:** a bar's length encodes its value, so the axis must start at  zero. Use points instead of bars when zero is not meaningful.- **Order:** sort categories by their value with `forcats::fct_reorder()`  unless the categories have a natural order, such as age groups.- **Missing values:** ggplot2 drops rows with missing values and warns.  Read the warning and count what was dropped before you trust the figure. ## Keep it plain - Label axes with readable names and units, using `labs()`. Variable names  such as `sbp_mean` are not labels.- Do not add titles, annotations, or custom colors to an exploratory figure.  That is publication work.- Do not save every exploratory figure. Show it in the notebook. Save a figure  only when someone needs the file. ## Example ```rtheme_set(theme_minimal()) ggplot(survey, aes(x = systolic_bp)) +  geom_histogram(binwidth = 5) +  labs(x = "Systolic blood pressure (mmHg)", y = "People") ggplot(survey, aes(x = age_group, y = systolic_bp)) +  geom_boxplot(outlier.shape = NA) +  geom_jitter(width = 0.2, alpha = 0.2) +  labs(x = "Age group", y = "Systolic blood pressure (mmHg)")``` ## In a Framework project Projects with a `settings.yml` use the framework R package. Read data with`data_read()`, not `read.csv()` or `readr`. If ggplot2 is not installed orlisted in the project's packages, add it with `package_add("ggplot2")` insteadof `install.packages()`. When a figure must be kept, save it with`save_figure(plot, "name")`. 

Connections, credentials, caching, and more

Framework also manages database connections, project secrets, slow computations, and more. Read the documentation for R, Python, or the command line to see everything Framework can do.

Database connections

Name each connection once in settings/connections.yml. Query it by name from any script or notebook.

measurements <- db_query(  "SELECT * FROM measurements",  "study_db")

Secure credential management

scaffold() loads .env into the session. Settings read secrets with env(). The .gitignore template keeps .env out of Git.

# settings/connections.ymlstudy_db:  host: env("DB_HOST")  password: env("DB_PASSWORD")

Result caching

Keep slow results between runs. Framework recomputes a value when it expires or when you refresh it.

fit <- cache_remember(  "bootstrap-fit",  run_bootstrap(penguins),  expire_after = "1 day")

Get started with Framework

Create a Framework Cloud account

or read the documentation