AE 18: Prompt engineering

Application exercise
R
Python
Modified

November 3, 2025

12_plot-image-1

library(readr)
library(ellmer)
library(ggplot2)

# Step 1: Make a scatter plot of the penguins dataset
data("penguins")

ggplot(data = penguins, aes(x = flipper_len, y = bill_len)) +
  geom_point(aes(color = species, shape = species), size = 3, alpha = 0.8) +
  geom_smooth(method = "lm", se = FALSE, aes(color = species)) +
  theme_minimal() +
  scale_color_manual(values = c("darkorange", "purple", "cyan4")) +
  labs(
    title = "Flipper and bill length",
    subtitle = "Dimensions for Adelie, Chinstrap and Gentoo Penguins at Palmer Station LTER",
    x = "Flipper length (mm)",
    y = "Bill length (mm)",
    color = "Penguin species",
    shape = "Penguin species"
  )

# Step 2: Ask Claude 4 Sonnet to interpret the plot.
# (Hint: see `content_image_...`)
chat <- chat("____", echo = "output")
chat$chat(
  "Interpret this plot.",
  ____()
)
# %%
import chatlas
import dotenv
from matplotlib import pyplot as plt
from palmerpenguins import load_penguins
from plotnine import (
    aes,
    geom_point,
    ggplot,
    labs,
    theme_minimal,
)

dotenv.load_dotenv()

# %%
# Step 1: Create a scatter plot of flipper length vs bill length for penguins
penguins = load_penguins()
penguins

# %%
p = (
    ggplot(penguins, aes(x="flipper_length_mm", y="bill_length_mm"))
    + geom_point(color="steelblue", size=3, alpha=0.8)
    + theme_minimal()
    + labs(
        title="Flipper and bill length",
        subtitle="Dimensions for Adelie, Chinstrap and Gentoo Penguins at Palmer Station LTER",
        x="Flipper length (mm)",
        y="Bill length (mm)",
    )
)

p.show()

# %%
# Register the plot with matplotlib's current figure
plt.figure(p.draw())

# %% [markdown]
# Step 2: Ask Claude 4 Sonnet to interpret the plot.
# (Hint: see `content_image_...`)

# %%
chat = chatlas.ChatAuto("____")
chat.chat(
    "Interpret this plot.",
    chatlas.____(),
)

13_plot-image-2

library(readr)
library(ellmer)
library(ggplot2)

# Step 1: This time, we're going to replace our mtcars scatter plot with a plot
# of uniform random noise.
m <- 32
u <- (seq_len(floor(sqrt(m))) - 0.5) / floor(sqrt(m))
grid <- as.matrix(expand.grid(x = u, y = u))

eps <- 1 / (2 * sqrt(m))
jitter <- matrix(runif(length(grid), -eps, eps), ncol = 2)
grid_jitter <- pmin(pmax(grid + jitter, 0), 1)

ggplot() +
  aes(x = grid_jitter[, 1], y = grid_jitter[, 2]) +
  geom_point(color = "steelblue", size = 3, alpha = 0.8) +
  theme_minimal() +
  labs(
    title = "Flipper and bill length",
    subtitle = "Dimensions for Adelie, Chinstrap and Gentoo Penguins at Palmer Station LTER",
    x = "Flipper length (mm)",
    y = "Bill length (mm)"
  )

# Step 2: Ask Claude 4 Sonnet to interpret the plot. How does it do this time?
chat <- chat("anthropic/claude-sonnet-4-20250514", echo = "output")
chat$chat(
  "Interpret this plot.",
  content_image_plot()
)

# Step 3: Work with a partner to improve the prompt to get a better
# interpretation.
# %%
from math import floor, sqrt

import chatlas
import dotenv
import numpy as np
from matplotlib import pyplot as plt
from plotnine import aes, geom_point, ggplot, labs, theme_minimal

dotenv.load_dotenv()

# %% [markdown]
# Step 1: This time, we're going to replace our mtcars scatter plot with a plot
# of uniform random noise.

# %%
m = 32

g = floor(sqrt(m))
u = (np.arange(1, g + 1) - 0.5) / g
xx, yy = np.meshgrid(u, u)
grid = np.column_stack([xx.ravel(), yy.ravel()])

# small jitter to avoid perfect lattice, scaled to cell size
eps = 1.0 / (2.0 * sqrt(m))
jitter = np.random.uniform(-eps, eps, size=grid.shape)
grid_jitter = np.clip(grid + jitter, 0.0, 1.0)

x = grid_jitter[:, 0]
y = grid_jitter[:, 1]

p = (
    ggplot(aes(x=x, y=y))
   + geom_point(color="steelblue", size=3, alpha=0.8)
    + theme_minimal()
    + labs(
        title="Flipper and bill length",
        subtitle="Dimensions for Adelie, Chinstrap and Gentoo Penguins at Palmer Station LTER",
        x="Flipper length (mm)",
        y="Bill length (mm)",
    )
)

p.show()

# %% [markdown]
# Step 2: Ask Claude 4 Sonnet to interpret the plot. How does it do this time?

# %%
# Register the plot with matplotlib's current figure
plt.figure(p.draw())

chat = chatlas.ChatAuto("anthropic/claude-sonnet-4-20250514")
chat.chat(
    "Interpret this plot.",
    chatlas.content_image_plot(),
)

# %% [markdown]
# Step 3: Work with a partner to improve the prompt to get a better
# interpretation.

14_quiz-game-1

library(shiny)
library(bslib)
library(ellmer)
library(shinychat)

# UI ---------------------------------------------------------------------------

ui <- page_fillable(
  chat_mod_ui("chat")
)

# Server -----------------------------------------------------------------------

server <- function(input, output, session) {
  client <- chat(
    "anthropic/claude-3-7-sonnet-20250219",
    # Step 1: Edit `prompt.md` to get the model to play the quiz game.
    system_prompt = interpolate_file(
      here::here("_exercises/14_quiz-game-1/prompt.md")
    )
  )

  chat <- chat_mod_server("chat", client)

  observe({
    # Note: this starts the game when the app launches
    chat$update_user_input(
      value = "Let's play the quiz game!",
      submit = TRUE
    )
  })
}

shinyApp(ui, server)
import chatlas
import dotenv
from pyhere import here
from shiny import App, reactive, ui

dotenv.load_dotenv()


# UI ---------------------------------------------------------------------------

app_ui = ui.page_fillable(
    ui.chat_ui("chat"),
)


def server(input, output, session):
    chat_ui = ui.Chat(id="chat")

    client = chatlas.ChatAnthropic(
        model="claude-3-7-sonnet-20250219",
        # Step 1: Edit `prompt.md` to get the model to play the quiz game.
        system_prompt=here("_exercises/14_quiz-game-1/prompt.md").read_text(),
    )

    @chat_ui.on_user_submit
    async def handle_user_input(user_input: str):
        response = await client.stream_async(user_input, content="all")
        await chat_ui.append_message_stream(response)

    @reactive.effect
    def _():
        # Note: this start the game when the app launches
        chat_ui.update_user_input(value="Let's play the quiz game!", submit=True)


app = App(app_ui, server)

Acknowledgments