Code
library(tidyverse)
library(plotly)
p1 <- ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv))
ggplotly(p1)Open slides in new window Download PDF of slides
Together we’ll make some plots with plotly::ggplotly() and ggiraph::girafe() (see this for a general ggplotly tutorial)
I’ll post all the final code on the course website when we’re done.
Together we’ll make an interactive dashboard about the Palmer Penguins.
I’ll post all the final code on the course website when we’re done.
Together we’ll do this:
I’ll post all the final code on the course website when we’re done.
Plotly:
{ggiraph}:
library(ggiraph)
mpg_fancy <- mpg |>
mutate(
carname = glue::glue(
"{str_to_title(manufacturer)} {str_to_title(model)}"
)
)
p2 <- ggplot(mpg_fancy, aes(x = displ, y = hwy, color = drv)) +
geom_point_interactive(aes(tooltip = carname, data_id = carname), size = 2) +
theme_minimal()
girafe(ggobj = p2)See here for the code for this dashboard (and click here to open the dashboard in a new tab):
Live rnorm() (see code here):
Original Shiny k-means example
Recreation with webR and OJS inputs (see code here):
vars = ["Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"]
viewof xcol = Inputs.select(vars, {
label: "X variable",
value: "Sepal.Length"
})
viewof ycol = Inputs.select(vars, {
label: "Y variable",
value: "Sepal.Width"
})
viewof clusters = Inputs.range([1, 9], {
label: "Clusters",
step: 1,
value: 3
})This required hours of fighting with Claude and it is so janky and awful
This is R code that sends the faithful data to OJS:
This is OJS code for making input options:
viewof nbins = Inputs.select(
[10, 20, 35, 50],
{label: "Number of bins"}
)
viewof individual_obs = Inputs.checkbox(["Show individual observations"], {
value: []
})
viewof density = Inputs.checkbox(["Show density estimate"], {
value: []
})
viewof bw_adjust = Inputs.range([0.2, 2], {
label: "Bandwidth adjustment:",
step: 0.2,
value: 1
})This is OJS code for plotting the data:
faithful_js = transpose(faithful)
Plot.plot({
height: 300,
y: { label: "Density" },
x: { label: "Duration (minutes)" },
marks: [
// Probability-based histogram
Plot.rectY(
faithful_js,
Plot.binX(
{ y: (bin, { x1, x2 }) => bin.length / faithful_js.length / (x2 - x1) }, // Normalize to probability density
{ x: "eruptions", thresholds: nbins } // Use nbins for threshold count
)
),
// Zero line
Plot.ruleY([0]),
// Individual observations
// Individual observations (separate layer for ticks)
individual_obs.length > 0
? Plot.dot(faithful_js, {
x: "eruptions",
y: (d) => jitter(d.eruptions, 0.05), // Deterministic jitter based on data
// y: () => Math.random() * 0.05, // Jitter points randomly along the y-axis
stroke: "white",
fill: "red",
})
: null,
// Density line
density.length > 0
? Plot.line(
densityEstimate(
faithful_js.map((d) => d.eruptions),
bw_adjust // Bandwidth adjustment
),
{ x: "x", y: "density", stroke: "blue" }
)
: null,
].filter((d) => d !== null),
});function densityEstimate(values, bwAdjust) {
const kde = kernelDensityEstimator(
kernelEpanechnikov(0.2 * bwAdjust), // Kernel function
d3.scaleLinear().domain(d3.extent(values)).ticks(200) // Evaluate KDE at 200 equally spaced points
);
return kde(values);
}
// Kernel density estimator function
function kernelDensityEstimator(kernel, X) {
return function (sample) {
return X.map((x) => ({
x: x,
density: d3.mean(sample, (v) => kernel(x - v)), // Adjusted scaling
}));
};
}
// Epanechnikov kernel function
function kernelEpanechnikov(bandwidth) {
return function (u) {
return Math.abs(u /= bandwidth) <= 1 ? (0.75 * (1 - u * u)) / bandwidth : 0;
};
}
// Deterministic jitter function because Math.random() doesn't support seeds
function jitter(value, range) {
const hash = Math.sin(value) * 10000; // Generate pseudo-random hash based on value
return (hash - Math.floor(hash)) * range; // Scale hash to the desired range
}This required literally 8 minutes of reading the documentation.
Here’s OJS code for creating the interactive inputs:
viewof nbins_r = Inputs.select(
[10, 20, 35, 50],
{label: "Number of bins"}
)
viewof individual_obs_r = Inputs.toggle({
label: "Show individual observations",
value: false
})
viewof density_r = Inputs.toggle({
label: "Show density estimate",
value: false
})
viewof bw_adjust_r = density_r
? Inputs.range([0.2, 2], {
label: "Bandwidth adjustment:",
step: 0.2,
value: 1
})
: html`<input type="range" value="1" style="display:none">`This is the R code for connecting to those OJS inputs and using them live:
```{webr}
#| autorun: true
#| echo: false
#| input:
#| - nbins_r
#| - individual_obs_r
#| - density_r
#| - bw_adjust_r
hist(
faithful$eruptions,
probability = TRUE,
breaks = as.numeric(nbins_r),
xlab = "Duration (minutes)",
main = "Geyser eruption duration"
)
if (individual_obs_r) {
rug(faithful$eruptions)
}
if (density_r) {
dens <- density(faithful$eruptions, adjust = bw_adjust_r)
lines(dens, col = "blue")
}
```Here’s OJS code for creating the interactive inputs:
numeric_vars = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]
categorical_vars = ["species", "island", "sex"]
viewof species_filter = Inputs.checkbox(
["Adelie", "Chinstrap", "Gentoo"],
{label: "Species to include", value: ["Adelie", "Chinstrap", "Gentoo"]}
)
viewof x_var = Inputs.select(numeric_vars, {
label: "X Variable",
value: "bill_length_mm"
})
viewof y_var = Inputs.select(numeric_vars, {
label: "Y Variable",
value: "bill_depth_mm"
})
viewof color_var = Inputs.select(categorical_vars, {
label: "Color by",
value: "species"
})
viewof show_trend_species = Inputs.toggle({
label: "Show species trends",
value: false
})
viewof show_trend_overall = Inputs.toggle({
label: "Show overall trend",
value: false
})