library(ggplot2)

Customize the 2D Gaussian Target

means <- c(x=1, y=1)
vars <- c(x=1, y=1)
corr <- -0.6
coordinates <- c("x", "y") # names of the coordinates, should match the names in the means and vars vectors.

Define the Marginal Distributions

Since the marginal distributions of x and y are both normal, a single function does the job.

# to sample the target p(x|y=1), use sample_marginal("x", 1)
sample_marginal <- function(sampled_coor = "x", other_value = 1){
  other_coor <- coordinates[coordinates != sampled_coor]  # "y" if sampled_var is "x"
  marginal_mean <- means[sampled_coor] + 
    corr * 
    sqrt(vars[sampled_coor]/vars[other_coor]) *
    (other_value - means[other_coor])
  
  marginal_var <- vars[sampled_coor] * (1-corr^2)
  
  return(rnorm(n=1, mean=marginal_mean, sd=sqrt(marginal_var)))
}

Define Each Iteration of Gibbs Sampling

# systematic scan Gibbs, simulates transition from prev to new 
sys_gibbs_step <- function(prev, order = coordinates){
  new <- prev
  for (coordinate in order){
    remaining_coord <- order[order != coordinate]  # the coord. not being updated
    new[coordinate] <- sample_marginal(sampled_var = coordinate,
                                      other_value = prev[remaining_coord])
  }
  return(new)
}

# random scan Gibbs
random_gibbs_step <- function(prev){
  new <- prev
  coordinate <- sample(coordinates, size=1) # sample the coordinate to be updated
  remaining_coor <- coordinates[coordinates != coordinate]
  new[coordinate] <- sample_marginal(sampled_coor = coordinate,
                                      other_value = prev[remaining_coor])
  return(new)
}

Running Gibbs Sampling

# initialize
n <- 50000  # takes a while... use a smaller sample size if your computer dies.
samples <- data.frame(x = numeric(length = n),
                      y = numeric(length = n))  # each realization is 2d.
previous_state <- c(x = xmean, y = ymean)

for (k in 1:n){
  previous_state <- random_gibbs_step(previous_state)  
  # previous_state <- sys_gibbs_step(previous_state)
  samples[k,] <- previous_state
}

Plotting the Samples

Beware that this first plot looks like crap if your sample size is small.

ggplot(data = samples, aes(x=x, y=y)) +
  geom_bin2d(bins = sqrt(n)) +
  scale_fill_continuous(type = "viridis") +
  theme_minimal()

So use this instead.

ggplot(data = samples, aes(x=x, y=y)) +
  geom_point(alpha = 5/sqrt(n)) +
  theme_minimal() 

LS0tCnRpdGxlOiAiR2liYnMgU2FtcGxpbmcgaW4gUiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKYGBgCgojIEN1c3RvbWl6ZSB0aGUgMkQgR2F1c3NpYW4gVGFyZ2V0CmBgYHtyfQptZWFucyA8LSBjKHg9MSwgeT0xKQp2YXJzIDwtIGMoeD0xLCB5PTEpCmNvcnIgPC0gLTAuNgpjb29yZGluYXRlcyA8LSBjKCJ4IiwgInkiKSAjIG5hbWVzIG9mIHRoZSBjb29yZGluYXRlcywgc2hvdWxkIG1hdGNoIHRoZSBuYW1lcyBpbiB0aGUgbWVhbnMgYW5kIHZhcnMgdmVjdG9ycy4KYGBgCgojIERlZmluZSB0aGUgTWFyZ2luYWwgRGlzdHJpYnV0aW9ucwpTaW5jZSB0aGUgbWFyZ2luYWwgZGlzdHJpYnV0aW9ucyBvZiB4IGFuZCB5IGFyZSBib3RoIG5vcm1hbCwgYSBzaW5nbGUgZnVuY3Rpb24gZG9lcyB0aGUgam9iLgpgYGB7cn0KIyB0byBzYW1wbGUgdGhlIHRhcmdldCBwKHh8eT0xKSwgdXNlIHNhbXBsZV9tYXJnaW5hbCgieCIsIDEpCnNhbXBsZV9tYXJnaW5hbCA8LSBmdW5jdGlvbihzYW1wbGVkX2Nvb3IgPSAieCIsIG90aGVyX3ZhbHVlID0gMSl7CiAgb3RoZXJfY29vciA8LSBjb29yZGluYXRlc1tjb29yZGluYXRlcyAhPSBzYW1wbGVkX2Nvb3JdICAjICJ5IiBpZiBzYW1wbGVkX3ZhciBpcyAieCIKICBtYXJnaW5hbF9tZWFuIDwtIG1lYW5zW3NhbXBsZWRfY29vcl0gKyAKICAgIGNvcnIgKiAKICAgIHNxcnQodmFyc1tzYW1wbGVkX2Nvb3JdL3ZhcnNbb3RoZXJfY29vcl0pICoKICAgIChvdGhlcl92YWx1ZSAtIG1lYW5zW290aGVyX2Nvb3JdKQogIAogIG1hcmdpbmFsX3ZhciA8LSB2YXJzW3NhbXBsZWRfY29vcl0gKiAoMS1jb3JyXjIpCiAgCiAgcmV0dXJuKHJub3JtKG49MSwgbWVhbj1tYXJnaW5hbF9tZWFuLCBzZD1zcXJ0KG1hcmdpbmFsX3ZhcikpKQp9CmBgYAoKIyBEZWZpbmUgRWFjaCBJdGVyYXRpb24gb2YgR2liYnMgU2FtcGxpbmcKYGBge3J9CiMgc3lzdGVtYXRpYyBzY2FuIEdpYmJzLCBzaW11bGF0ZXMgdHJhbnNpdGlvbiBmcm9tIHByZXYgdG8gbmV3IApzeXNfZ2liYnNfc3RlcCA8LSBmdW5jdGlvbihwcmV2LCBvcmRlciA9IGNvb3JkaW5hdGVzKXsKICBuZXcgPC0gcHJldgogIGZvciAoY29vcmRpbmF0ZSBpbiBvcmRlcil7CiAgICByZW1haW5pbmdfY29vcmQgPC0gb3JkZXJbb3JkZXIgIT0gY29vcmRpbmF0ZV0gICMgdGhlIGNvb3JkLiBub3QgYmVpbmcgdXBkYXRlZAogICAgbmV3W2Nvb3JkaW5hdGVdIDwtIHNhbXBsZV9tYXJnaW5hbChzYW1wbGVkX3ZhciA9IGNvb3JkaW5hdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3RoZXJfdmFsdWUgPSBwcmV2W3JlbWFpbmluZ19jb29yZF0pCiAgfQogIHJldHVybihuZXcpCn0KCiMgcmFuZG9tIHNjYW4gR2liYnMKcmFuZG9tX2dpYmJzX3N0ZXAgPC0gZnVuY3Rpb24ocHJldil7CiAgbmV3IDwtIHByZXYKICBjb29yZGluYXRlIDwtIHNhbXBsZShjb29yZGluYXRlcywgc2l6ZT0xKSAjIHNhbXBsZSB0aGUgY29vcmRpbmF0ZSB0byBiZSB1cGRhdGVkCiAgcmVtYWluaW5nX2Nvb3IgPC0gY29vcmRpbmF0ZXNbY29vcmRpbmF0ZXMgIT0gY29vcmRpbmF0ZV0KICBuZXdbY29vcmRpbmF0ZV0gPC0gc2FtcGxlX21hcmdpbmFsKHNhbXBsZWRfY29vciA9IGNvb3JkaW5hdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3RoZXJfdmFsdWUgPSBwcmV2W3JlbWFpbmluZ19jb29yXSkKICByZXR1cm4obmV3KQp9CmBgYAoKIyBSdW5uaW5nIEdpYmJzIFNhbXBsaW5nCmBgYHtyfQojIGluaXRpYWxpemUKbiA8LSA1MDAwMCAgIyB0YWtlcyBhIHdoaWxlLi4uIHVzZSBhIHNtYWxsZXIgc2FtcGxlIHNpemUgaWYgeW91ciBjb21wdXRlciBkaWVzLgpzYW1wbGVzIDwtIGRhdGEuZnJhbWUoeCA9IG51bWVyaWMobGVuZ3RoID0gbiksCiAgICAgICAgICAgICAgICAgICAgICB5ID0gbnVtZXJpYyhsZW5ndGggPSBuKSkgICMgZWFjaCByZWFsaXphdGlvbiBpcyAyZC4KcHJldmlvdXNfc3RhdGUgPC0gYyh4ID0geG1lYW4sIHkgPSB5bWVhbikKCmZvciAoayBpbiAxOm4pewogIHByZXZpb3VzX3N0YXRlIDwtIHJhbmRvbV9naWJic19zdGVwKHByZXZpb3VzX3N0YXRlKSAgCiAgIyBwcmV2aW91c19zdGF0ZSA8LSBzeXNfZ2liYnNfc3RlcChwcmV2aW91c19zdGF0ZSkKICBzYW1wbGVzW2ssXSA8LSBwcmV2aW91c19zdGF0ZQp9CmBgYAoKIyBQbG90dGluZyB0aGUgU2FtcGxlcwpCZXdhcmUgdGhhdCB0aGlzIGZpcnN0IHBsb3QgbG9va3MgbGlrZSBjcmFwIGlmIHlvdXIgc2FtcGxlIHNpemUgaXMgc21hbGwuCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHNhbXBsZXMsIGFlcyh4PXgsIHk9eSkpICsKICBnZW9tX2JpbjJkKGJpbnMgPSBzcXJ0KG4pKSArCiAgc2NhbGVfZmlsbF9jb250aW51b3VzKHR5cGUgPSAidmlyaWRpcyIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgClNvIHVzZSB0aGlzIGluc3RlYWQuCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHNhbXBsZXMsIGFlcyh4PXgsIHk9eSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gNS9zcXJ0KG4pKSArCiAgdGhlbWVfbWluaW1hbCgpIApgYGA=