Skip to main content
AI in Production 2026 is now open for talk proposals.
Share insights that help teams build, scale, and maintain stronger AI systems.
items
Menu
  • About
    • Overview 
    • Join Us  
    • Community 
    • Contact 
  • Training
    • Overview 
    • Course Catalogue 
    • Public Courses 
  • Posit
    • Overview 
    • License Resale 
    • Managed Services 
    • Health Check 
  • Data Science
    • Overview 
    • Visualisation & Dashboards 
    • Open-source Data Science 
    • Data Science as a Service 
    • Gallery 
  • Engineering
    • Overview 
    • Cloud Solutions 
    • Enterprise Applications 
  • Our Work
    • Blog 
    • Case Studies 
    • R Package Validation 
    • diffify  

Vetiver: MLOps for Python

Author: Myles Mitchell

Published: February 27, 2025

tags: python, vetiver, machine-learning, production, mlops

This post is the fourth in our series on MLOps with vetiver:

  • Part 1: Vetiver: First steps in MLOps
  • Part 2: Vetiver: Model Deployment
  • Part 3: Vetiver: Monitoring Models in Production
  • Part 4: Vetiver: MLOps for Python (this post)

Parts 1 to 3 introduced the {vetiver} package for R and outlined its far-reaching applications in MLOps. But did you know that this package is also available in Python? In this post we will provide a brief outline to getting your Python models into production using vetiver for Python.

Do you use Professional Posit Products? If so, check out our managed Posit services

Installation

Like any other Python package on PyPI, vetiver can be installed using pip. Let’s set up a virtual environment and install all of the packages that will be covered in this blog:

python -m venv venv/
source venv/bin/activate
pip install vetiver pandas pyjanitor scikit-learn pins

Check out our previous blog about virtual environments in Python for more details.

Data

We will be working with the World Health Organisation Life Expectancy data which provides the annual average life expectancy in a number of countries. This can be downloaded from Kaggle:

import pandas as pd

url = "https://www.kaggle.com/api/v1/datasets/download/kumarajarshi/life-expectancy-who"
data = pd.read_csv(url, compression = "zip")
data.head()
#>        Country  Year  ... Income composition of resources  Schooling
#> 0  Afghanistan  2015  ...                           0.479       10.1
#> 1  Afghanistan  2014  ...                           0.476       10.0
#> 2  Afghanistan  2013  ...                           0.470        9.9
#> 3  Afghanistan  2012  ...                           0.463        9.8
#> 4  Afghanistan  2011  ...                           0.454        9.5
#> 
#> [5 rows x 22 columns]

Let’s drop missing data, clean up the column names and select a subset of the variables to work with:

import janitor

data = data.dropna()
data = data.clean_names(strip_underscores=True)
data = data[[
    "life_expectancy",
    "percentage_expenditure",
    "total_expenditure",
    "population",
    "bmi",
    "schooling",
]]
data.head()
#>    life_expectancy  percentage_expenditure  ...   bmi  schooling
#> 0             65.0               71.279624  ...  19.1       10.1
#> 1             59.9               73.523582  ...  18.6       10.0
#> 2             59.9               73.219243  ...  18.1        9.9
#> 3             59.5               78.184215  ...  17.6        9.8
#> 4             59.2                7.097109  ...  17.2        9.5
#> 
#> [5 rows x 6 columns]

Vetiver is compatible with models built in scikit-learn, PyTorch, XGBoost and statsmodels. The actual modelling process is not so important in this blog. We will be more interested in how we go about taking this model into production using vetiver. So let’s go with a simple K-Nearest Neighbour model built using scikit-learn:

from sklearn.neighbors import KNeighborsRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

target = "life_expectancy"
covariates = [
    "percentage_expenditure",
    "total_expenditure",
    "population",
    "bmi",
    "schooling",
]
y = data[target]
X = data[covariates]

model = Pipeline(
    [
        ("transform", StandardScaler()),
        ("model", KNeighborsRegressor()),
    ]
)
model.fit(X, y)
#> Pipeline(steps=[('transform', StandardScaler()),
#>                 ('model', KNeighborsRegressor())])

Let’s break down what’s happened here:

  • We selected our target variable (life expectancy) and the covariates (features) that will be used to predict the target.
  • We constructed a modelling pipeline which includes:
    • Preprocessing of input data via standardisation.
    • K-Nearest Neighbours regression.
  • In the final step, we fitted our model to the training data.

Usually at this point we would evaluate how our model performs on some unseen test data. However, for brevity we’ll now go straight to the MLOps steps.

MLOps

In a typical MLOps workflow, we are setting up a continuous cycle in which our trained model is deployed to a cloud environment, monitored in this environment, and then retrained on the latest data. The cycle repeats so that we are always maintaining a high model performance and avoiding the dreaded model drift (more on this later).

A flow chart showing the typical MLOps workflow. We begin by importing and tidying our data sets. We then fit a model to this data, and this model is versioned and deployed to the cloud. After it is deployed we then monitor the model, and we repeat the cycle by retraining the model on the latest data to maintain an acceptable performance.

From the diagram above, the crucial steps that set this workflow apart from a typical data science project are model versioning, deployment and monitoring. We will go through each of these in turn using vetiver.

Before we can begin, we must convert our scikit-learn model into a “vetiver model”:

import vetiver

v_model = vetiver.VetiverModel(model, model_name="KNN", prototype_data=X)
print(type(v_model))
#> <class 'vetiver.vetiver_model.VetiverModel'>
print(v_model.description)
#> A scikit-learn Pipeline model
print(v_model.metadata)
#> VetiverMeta(user={}, version=None, url=None, required_pkgs=['scikit-learn'], python_version=(3, 10, 12, 'final', 0))

Our VetiverModel object contains model metadata and dependencies (including the Python packages used to train it and the current Python version). The model_name will be used to identify the model later on, and the prototype_data will provide some example data for the model API (more on this below).

Model versioning

In a cycle where our model is continuously being retrained, it is important to ensure that we can retrieve any models that have previously been deployed. Vetiver utilises the pins package for model storage. A pin is simply a Python object (could be a variable, data frame, function, …) which can be stored and retrieved at a later time. Pins are stored in “pins boards”. Examples include:

  • Local storage on your device
  • Google Drive
  • Amazon S3
  • Posit Connect

Let’s set up a temporary pins board locally for storing our model:

from pins import board_temp

model_board = board_temp(
    versioned=True, allow_pickle_read=True
)
vetiver.vetiver_pin_write(model_board, v_model)
#> Model Cards provide a framework for transparent, responsible reporting. 
#>  Use the vetiver `.qmd` Quarto template as a place to start, 
#>  with vetiver.model_card()
#> Writing pin:
#> Name: 'KNN'
#> Version: 20250220T141808Z-af3d5

Enabling allow_pickle_read will allow quick reloading of the model later on, whenever we need it.

At this stage our VetiverModel object is now stored as a pin, and we can view the full list of “KNN” model versions using:

model_board.pin_versions("KNN")
#>               created   hash                 version
#> 0 2025-02-20 14:18:08  af3d5  20250220T141808Z-af3d5

As expected, we only have one version stored so far!

Model deployment

If we want to share our model with other users (colleagues, stakeholders, customers) we should deploy it to an endpoint on the cloud where it can be easily shared. To keep things simple for this blog, and to ensure the code examples provided here are fully reproducible, we will just deploy our model to the localhost.

First we have to construct a model API. This is a simple interface which takes some input and gives us back some model predictions. Crucially, APIs can be hosted on the cloud where they can receive input data via HTTP requests.

Our VetiverModel object already contains all of the info necessary to build an API using the FastAPI framework:

app = vetiver.VetiverAPI(v_model, check_prototype=True)

Running app.run(port=8080) will start a local server for the model API on port 8080. We are then presented with a simple graphical interface in which we can run basic queries and generate predictions using our model. The prototype_data argument which we defined when constructing our VetiverModel (see above) is used here to provide some example input data for queries:

A screenshot of the user interface for the deployed model API. Some example input data is shown in a JSON format which can be ingested by the model. A try button is provided for the user to generate model predictions using this data. There are also buttons to clear the example data and fill in a new example.

Alternatively we can also submit queries from the command line. The graphical interface above provides template curl commands which can be copied into the command line and executed against the model. For example, the input data shown in the above screenshot can be fed into the model via a POST request:

curl -X POST "http://127.0.0.1:8080/predict" \
 -H "Accept: application/json" \
 -H "Content-Type: application/json" \
 -d '[{"percentage_expenditure":71.27962362,"total_expenditure":8.16,"population":33736494,"bmi":19.1,"schooling":10.1}]' \

The same command would work for querying APIs on the cloud as long as the IP address for the API endpoint (here it is http://127.0.0.1, which points to the localhost) is updated accordingly.

Deploying your model locally is a great way to test that your API behaves as you expect. What’s more, it’s free and does not require setting up an account with a cloud provider! But how would we go about deploying our model to the cloud?

If you already have a server on Posit Connect, it’s just a case of running vetiver.deploy_rsconnect() (see the Posit vetiver documentation for more details). If you don’t have Posit Connect, not to worry! Instead you can start by running:

vetiver.prepare_docker(model_board, "KNN")

This command is doing a lot of heavy lifting behind the scenes:

  • Lists the Python package dependencies in a vetiver_requirements.txt file.
  • Stores the Python code for the model API in an app.py file.
  • Creates a Dockerfile containing the Python version requirement for the model and the docker commands for building and running the API. An example is shown below:
# # Generated by the vetiver package; edit with care
# start with python base image
FROM python:3.10

# create directory in container for vetiver files
WORKDIR /vetiver

# copy  and install requirements
COPY vetiver_requirements.txt /vetiver/requirements.txt

#
RUN pip install --no-cache-dir --upgrade -r /vetiver/requirements.txt

# copy app file
COPY app.py /vetiver/app/app.py

# expose port
EXPOSE 8080

# run vetiver API
CMD ["uvicorn", "app.app:api", "--host", "0.0.0.0", "--port", "8080"]

With these files uploaded to the cloud server of your choosing, the docker build command will take care of the rest. This process can be automated on AWS, Google Cloud Run, Azure, and many other cloud platforms.

Model monitoring

Success! Your model is now deployed and your users are interacting with it. But this is only the beginning…

Data changes! Over time you will notice various aspects of your data changing in unexpected ways:

  • The way the data is distributed may change (data drift).
  • The relationship between the target variable and covariates may change (concept drift).

These two processes will conspire to create model drift, where your model predictions start to drift away from the true values. This is why MLOps is not simply a one-off deployment. It is a continuous cycle in which you will be retraining your model on the latest data on a regular basis.

While we will not be providing a full worked example of model drift here, we will just mention some helpful functions provided by vetiver to deal with this problem:

  • vetiver.compute_metrics(): computes keys metrics at specified time intervals, allowing us to understand how the model performance varies over time.
  • vetiver.pin_metrics(): stores the model metrics in a pins board for future retrieval.
  • vetiver.plot_metrics(): plots the metrics over time.

You can get an idea of how these Python methods can be used by reading our previous blog post where we monitored the model’s performance using vetiver for R.

The metrics can be entirely defined by the user, and might include the accuracy score for a classification model and the mean squared error for a regression model. We can also make use of predefined scoring functions from the sklearn.metrics library.

For more on model monitoring, check out the Posit vetiver documentation.

Summary

Hopefully by reading this post you will have a better understanding of MLOps and how to get started with MLOps in Python. Most importantly, you don’t have to be an expert in AWS or Azure to get started! Vetiver provides intuitive, easy-to-use functions for learning the crucial steps of MLOps including versioning your model, building a model API, and deploying your model using docker or Posit Connect.

For some further reading, check out:

  • Our previous blog posts on vetiver with R.
  • The Posit vetiver documentation.

Jumping Rivers Logo

Recent Posts

  • Start 2026 Ahead of the Curve: Boost Your Career with Jumping Rivers Training 
  • Should I Use Figma Design for Dashboard Prototyping? 
  • Announcing AI in Production 2026: A New Conference for AI and ML Practitioners 
  • Elevate Your Skills and Boost Your Career – Free Jumping Rivers Webinar on 20th November! 
  • Get Involved in the Data Science Community at our Free Meetups 
  • Polars and Pandas - Working with the Data-Frame 
  • Highlights from Shiny in Production (2025) 
  • Elevate Your Data Skills with Jumping Rivers Training 
  • Creating a Python Package with Poetry for Beginners Part2 
  • What's new for Python in 2025? 

Top Tags

  • R (236) 
  • Rbloggers (182) 
  • Pybloggers (89) 
  • Python (89) 
  • Shiny (63) 
  • Events (26) 
  • Training (23) 
  • Machine Learning (22) 
  • Conferences (20) 
  • Tidyverse (17) 
  • Statistics (14) 
  • Packages (13) 

Authors

  • Amieroh Abrahams 
  • Shane Halloran 
  • Russ Hyde 
  • Tim Brock 
  • Myles Mitchell 
  • Theo Roe 
  • Colin Gillespie 
  • Aida Gjoka 
  • Gigi Kenneth 
  • Osheen MacOscar 
  • Sebastian Mellor 
  • Keith Newman 
  • Pedro Silva 

Keep Updated

Like data science? R? Python? Stan? Then you’ll love the Jumping Rivers newsletter. The perks of being part of the Jumping Rivers family are:

  • Be the first to know about our latest courses and conferences.
  • Get discounts on the latest courses.
  • Read news on the latest techniques with the Jumping Rivers blog.

We keep your data secure and will never share your details. By subscribing, you agree to our privacy policy.

Follow Us

  • GitHub
  • Bluesky
  • LinkedIn
  • YouTube
  • Eventbrite

Find Us

The Catalyst Newcastle Helix Newcastle, NE4 5TG
Get directions

Contact Us

  • hello@jumpingrivers.com
  • + 44(0) 191 432 4340

Newsletter

Sign up

Events

  • North East Data Scientists Meetup
  • Leeds Data Science Meetup
  • Shiny in Production
British Assessment Bureau, UKAS Certified logo for ISO 9001 - Quality management British Assessment Bureau, UKAS Certified logo for ISO 27001 - Information security management Cyber Essentials Certified Plus badge
  • Privacy Notice
  • |
  • Booking Terms

©2016 - present. Jumping Rivers Ltd