查看原文
其他

R 语言笔记:绘制 3D 的主成分分析图(PCA),常用的几种方法

ecologyR ecologyR 2024-03-26

有时候,3D 的 PCA 图是很有用的,所以 PCA 显示 3 个轴在论文中(包括不少好论文)也偶尔可以看到。

例如:Plant chemical traits define functional and phylogenetic axes of plant biodiversity

https://onlinelibrary.wiley.com/doi/full/10.1111/ele.14262

怎么制作一个 3D 的 PCA 图

第一步,使用 vegan 包运行一个 PCA 分析,

#################################################################
##                           PCA 分析                          ##
###
##############################################################

# 包
library(tidyverse)
library(vegan)
library(lattice)
library(rayrender)

# 执行 PCA 分析
pca <- iris[, 1:4] |> 
  scale() |> 
  rda()

第二步,提取 PCA 得分数据(即获取 PC1、PC2、PC3 的坐标)

lab.pca1 <- sprintf("Axis 1 (%.2f%%)", pca_smr$cont$importance[21] * 100)
lab.pca2 <- sprintf("Axis 2 (%.2f%%)", pca_smr$cont$importance[22] * 100)
lab.pca3 <- sprintf("Axis 3 (%.2f%%)", pca_smr$cont$importance[23] * 100)

# 坐标提取:样点(花朵种类)
st_coords <- summary(pca, scaling = 2)$sites |> 
  as.data.frame() |> 
  mutate(type = iris$Species)

# 坐标提取:变量数据(花朵变量)
sp_coords <- (summary(pca, scaling = 2)$species / 2) |> 
  as.data.frame() |> 
  rownames_to_column(var = "var")

# 绘图
ggplot(sp_coords, aes(PC1, PC2, color = var)) +
  geom_point(data = st_coords, aes(PC1, PC2), inherit.aes = FALSE) +
  geom_segment(aes(x = 0, y = 0, xend = PC1, yend = PC2), arrow = grid::arrow(45, unit(10"pt"))) +
  scale_color_viridis_d() +
  labs(title = "Correlation plot (scaling = 2)") +
  coord_fixed() +
  theme_minimal(base_size = 16) +
  theme(panel.grid.minor = element_blank())
ggsave("ggplot2-PCA-2d.png", width = 5, height = 4, dpi = 600, bg = "white")

第三步,第一种方法,使用 scatterplot3d 包绘制一个 3D PCA 图(显示 3 个轴),

#################################################################
##                        常规的 3D 绘图                       ##
###
##############################################################

# 绘 3D 图(使用 scatterplot3d 或 lattice 包)
st.colors <- setNames(RColorBrewer::brewer.pal(nlevels(iris$Species), "Dark2"), levels(iris$Species))

png("scatterplot3d-PCA-3d.png", width = 5, height = 5, units = "in", res = 600)
scatterplot3d::scatterplot3d(
  st_coords$PC1,
  st_coords$PC2,
  st_coords$PC3,
  pch = 19,
  color = st.colors[st_coords$type],
  xlab = lab.pca1,
  ylab = lab.pca2,
  zlab = lab.pca3
)
legend("top", legend = levels(iris$Species), pch = 19, col = st.colors)
dev.off()

png("lattice-PCA-3d.png", width = 3, height = 3, units = "in", res = 600)
lattice::cloud(PC3 ~ PC1 * PC2, group = type, pch = 19, data = st_coords)
dev.off()

进阶!第四步,使用 rayrender 包,绘制带有光影效果 3D PCA 图。(光影的存在,可避免看不出散点在前还是在后的问题,只是炫酷而已
#################################################################
##                        立体感 3D 绘图                       ##
#################################################################

# 物种颜色
st.colors <- setNames(RColorBrewer::brewer.pal(nlevels(iris$Species), "BrBG"), levels(iris$Species))
spheres <- pmap_dfr(
  list(st_coords$PC1, st_coords$PC2, st_coords$PC3, st_coords$type),
  function(PC1, PC2, PC3, type) {
    sphere(
      PC1,
      PC2,
      PC3,
      radius = 0.1 / 1.5,
      material = diffuse(color = st.colors[type])
    )
  }
)

# 准备线条
l.axis <- 10
l.width <- 0.1 / 10
path.1 <- list(c(000), c(l.axis, 00), c(-l.axis, 00))
path.2 <- list(c(000), c(0, l.axis, 0), c(0, -l.axis, 0))
path.3 <- list(c(000), c(00, l.axis), c(00, -l.axis))

# 变量(及其标签)颜色
sp.colors <- setNames(RColorBrewer::brewer.pal(nlevels(factor(sp_coords$var)), "Set1"), levels(factor(sp_coords$var)))
paths <- pmap_dfr(
  list(sp_coords$PC1, sp_coords$PC2, sp_coords$PC3, sp_coords$var),
  function(PC1, PC2, PC3, var) {
    path(
      list(c(PC1, PC2, PC3), c(000)),
      width = l.width * 1.5,
      material = light(color = sp.colors[var], intensity = 2)
    )
  }
)

# 准备文字
h.text <- 0.1
lab.pca1 <- sprintf("Axis 1 (%.2f%%)", pca_smr$cont$importance[21] * 100)
lab.pca2 <- sprintf("Axis 2 (%.2f%%)", pca_smr$cont$importance[22] * 100)
lab.pca3 <- sprintf("Axis 3 (%.2f%%)", pca_smr$cont$importance[23] * 100)
texts <- pmap_dfr(
  list(sp_coords$var, sp_coords$PC1, sp_coords$PC2, sp_coords$PC3),
  function(lab, PC1, PC2, PC3) {
    text3d(
      label = str_to_title(str_replace(lab, "[.]"" ")),
      PC1 * 1.1,
      PC2 * 1.1,
      PC3 * 1.1,
      text_height = h.text * 1.3,
      material = light(color = sp.colors[lab], intensity = 2)
    )
  }
)

# 构造 3D 物体
f.scene <- function(angle1 = 0, angle2 = 0, angle3 = 0) {
  generate_ground(depth = -2, material = diffuse(checkercolor = "grey20")) |>
    add_object(
      group_objects(
        rbind(spheres, paths, texts) |> 
          # add_object(path(path.1, width = l.width / 3, type = "flat", material = light(intensity = 2))) |>
          # add_object(path(path.2, width = l.width / 3, type = "flat", material = light(intensity = 2))) |>
          # add_object(path(path.3, width = l.width / 3, type = "flat", material = light(intensity = 2))) |>
          add_object(text3d(-100, text_height = h.text, labe = lab.pca1, material = light(intensity = 2))) |> 
          add_object(text3d(010, text_height = h.text, labe = lab.pca2, material = light(intensity = 2))) |> 
          add_object(text3d(001, text_height = h.text, labe = lab.pca3, material = light(intensity = 2))),
        angle = c(angle1, angle2, angle3)
      )
    ) |> 
    add_object(sphere(x = -5, y = 7, z = 8, radius = 2, material = light(intensity = 20))) |>
    add_object(sphere(x = 5, y = 7, z = 8, radius = 2, material = light(intensity = 10)))
}
render_preview(f.scene(angle1 = 0, angle2 = 0, angle3 = 0), fov = 20, lookfrom = c(116), exponent = Inf)

# 渲染 3D 散点图
f.width <- f.height <- 1000 / 1; f.width
render_scene(
  f.scene(angle1 = 0, angle2 = 30, angle3 = 0),
  filename = "pca-3D.png",
  fov = 17,
  lookfrom = c(1410),
  aperture = 0,
  width = f.width,
  height = f.height,
  samples = 300,
  # clamp_value = 10, # 此处设置,增加渲染画质,耗时更长
  # min_variance = 5e-6, # 此处设置,极大增加渲染画质,耗时更长(可能长达 30 分钟)
  parallel = TRUE
)

效果如下,

记录之。

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存