library(tidyverse)
theme_set(theme_light())
36-315: Statistical Graphics and Visualization, Summer 2026
The truth is (almost) never linear
But often the linearity assumption is good enough
Many nonparametric/nonlinear regression methods
splines
local regression
generalized additive models
kernel regression
wavelets
Assume normality, but not linearity where \(f(x)\) is some unknown function \[Y_i \stackrel{\textsf{iid}}{\sim} N(f(X_i), \sigma^2)\]
Do not make explicit assumptions about the functional form of \(f\) (i.e., \(f\) is estimated from the data)
Intuition: Any nonlinear function is locally linear
Local linear regressions fits a bunch of… local linear regressions, and then glues them together
Local linear regression is basically weighted linear regression, where only “local units” get weight
\[\arg \min_{\beta_0,\beta_1} \sum_{i=1}^n (Y_i - \beta_0 - \beta_1 X_i)^2\]
\[\arg \min_{\beta_0,\beta_1} \sum_{i=1}^n w_i \cdot (Y_i - \beta_0 - \beta_1 X_i)^2\]
Local linear regression is exactly the same, except the weights depend on which \(x\) we want to estimate \(f(x)\)
Local regression at \(X=x_0\)
Compute the fraction \(k/n\) of observations who \(x_i\) are the closest to \(x_0\)
Assign a weight \(w_i = w(x_i, x_0)\) to each point in this local neighborhood, so that the closest/furthest has the highest/lowest weights. All but these \(k\) nearest neighbors get weight zero
Fit a weighted linear (least squares) regression
\[\text{arg }\underset{\beta_0, \beta_1}{\text{min}} \sum_i^n w_i(x_0) \cdot \big(Y_i - \beta_0 - \beta_1 X_i \big)^2\]
In local linear regression, we estimate \(f(X_i)\) and solve
\[\text{arg }\underset{\beta_0, \beta_1}{\text{min}} \sum_i^n w_i(x) \cdot \big(Y_i - \beta_0 - \beta_1 X_i \big)^2\]
geom_smooth() uses tri-cubic weighting:
\[w_i(d_i) = \begin{cases} (1 - |d_i|^3)^3, \text{ if } i \in \text{neighborhood of } x\\ 0 \text{ if } i \notin \text{neighborhood of } x \end{cases}\]
\(d_i\): distance between \(x\) and \(X_i\) scaled to be between 0 and 1
span: controls fraction of observations in neighborhood around the observation of interest
span = 0.75 (use 75% of the observations to fit local linear regression with weights)spanCreate a pairs plot to see all pairwise relationships in one plot
Pairs plot can include the various kinds of pairwise plots
Two quantitative variables: scatterplot
One categorical, one quantitative: side-by-side violins, stacked histograms, overlaid densities
Two categorical: stacked bars, side-by-side bars, mosaic plots
Can customize which figures are displayed in which panels
For more details, check out the package documentation here
ggplot2 customizationUse correlate() and scretch() functions from the corrr package to compute correlation matrix and pivot
Use geom_tile() to create a heatmap (tile map)
ggcorrplotDisplay each variable side-by-side on standardized axis
Connect observations with lines
Useful when working with a moderate number of observations and variables
(can be overwhelming with too many)
Adjust the transparency of the lines to handle overlap
By default, the y-axis is scaled by subtracting the mean and dividing by the standard deviation
Use scale = "uniminmax" so that minimum of the variable is 0 and the maximum is 1
This requires characterizing the distance between observations
Define distance beyond 2D:
Let \(\boldsymbol{x}_i = (x_{i1}, \dots, x_{ip})\) be a vector of \(p\) features for observation \(i\)
Question of interest: How “far away” is \(\boldsymbol{x}_i\) from \(\boldsymbol{x}_j\)?
When looking at a scatterplot, Euclidean distance is used \[d(\boldsymbol{x}_i, \boldsymbol{x}_j) = \sqrt{(x_{i1} - x_{j1})^2 + \dots + (x_{ip} - x_{jp})^2}\]
Let \(d(\boldsymbol{x}_i, \boldsymbol{x}_j)\) denote the pairwise distance between two observations \(i\) and \(j\)
Identity: \(\boldsymbol{x}_i = \boldsymbol{x}_j \Leftrightarrow d(\boldsymbol{x}_i, \boldsymbol{x}_j) = 0\)
Non-negativity: \(d(\boldsymbol{x}_i, \boldsymbol{x}_j) \geq 0\)
Symmetry: \(d(\boldsymbol{x}_i, \boldsymbol{x}_j) = d(\boldsymbol{x}_j, \boldsymbol{x}_i)\)
Triangle inequality: \(d(\boldsymbol{x}_i, \boldsymbol{x}_j) \leq d(\boldsymbol{x}_i, \boldsymbol{x}_k) + d(\boldsymbol{x}_k, \boldsymbol{x}_j)\)
Distance matrix: matrix \(D\) of all pairwise distances
\(D_{ij} = d(\boldsymbol{x}_i, \boldsymbol{x}_j)\)
Note: \(D_{ii} = 0\) and \(D_{ij} = D_{ji}\)
\[ D = \begin{pmatrix} 0 & D_{12} & \cdots & D_{1n} \\ D_{21} & 0 & \cdots & D_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ D_{n1} & \cdots & \cdots & 0 \end{pmatrix} \]
Preserve the distances as much as possible
Formally, MDS attempts to find \(\mathbf y_1, \ldots, \mathbf y_n \in \mathbb{R}^k\) whose distance matrix is approximately \(\mathbf D=(d_{ij}: \, i,j=1, \ldots , n)\) (the original distance matrix)
[1] "product_name" "size" "milk" "whip"
[5] "serv_size_m_l" "calories" "total_fat_g" "saturated_fat_g"
[9] "trans_fat_g" "cholesterol_mg" "sodium_mg" "total_carbs_g"
[13] "fiber_g" "sugar_g" "caffeine_mg"
Use cmdscale() function and supply a distance matrix d and the number of dimensions k
(usually k = 2 for visualization purposes)
What are the dimensions of the input and output?
How do we interpret the 2D projection?
Which of these movies are most similar/different?
mcu <- read_csv("https://raw.githubusercontent.com/qntkhvn/36-315-summer26/refs/heads/master/data/mcu.csv")
# names(mcu)
mcu_scaled <- mcu |>
select(-c(film, category, year)) |>
mutate(across(everything(), ~ .x / sd(.x, na.rm = TRUE)))
mcu_mds <- cmdscale(d = dist(mcu_scaled), k = 2)
mcu <- mcu |>
mutate(mds1 = mcu_mds[, 1], mds2 = mcu_mds[, 2])
library(ggrepel)
mcu |>
ggplot(aes(x = mds1, y = mds2)) +
geom_point(color = "darkblue", size = 2, alpha = 0.5) +
geom_text_repel(aes(label = film), alpha = 0.75, color = "black") +
labs(x = "MDS coordinate 1", y = "MDS coordinate 2")