Files

901 lines
33 KiB
R
Raw Permalink Normal View History

2026-08-03 18:42:47 +08:00
#!/usr/bin/env Rscript
# 启动子元件与进化树组合绘图 - 修复short_desc列问题
# 清理环境
rm(list = ls())
####----加载R包----####
required_packages <- c("tidyverse", "readxl", "writexl", "ggh4x",
"ggtree", "treeio", "ape", "patchwork", "aplot", "RColorBrewer", "ggnewscale")
missing_packages <- required_packages[!required_packages %in% installed.packages()[,"Package"]]
if(length(missing_packages) > 0) {
cat("安装缺少的包:", paste(missing_packages, collapse = ", "), "\n")
install.packages(missing_packages, repos = "https://cloud.r-project.org")
}
library(tidyverse)
library(readxl)
library(writexl)
library(ggh4x)
library(ggtree)
library(treeio)
library(ape)
library(patchwork)
library(aplot)
library(RColorBrewer)
library(ggnewscale)
####----函数定义:处理元件描述----####
create_short_descriptions <- function(cir_element.desc) {
cir_element.desc %>%
mutate(
description = str_trim(description),
description = str_replace_all(description, '""', '"'),
short_desc = case_when(
element == "Myb-binding site" ~ "Myb-site (RNR1b, cell cycle)",
element == "MYB recognition site" ~ "MYB-site (ProDH, dehydration)",
element == "CCGTCC motif" ~ "CCGTCC motif (H3, S-phase)",
str_detect(description, "^cis-acting element involved in the abscisic acid") ~ "ABA responsive",
str_detect(description, "^cis-acting regulatory element involved in light") ~ "Light responsive",
str_detect(description, "^cis-acting element involved in low-temperature") ~ "Cold responsive",
str_detect(description, "^cis-acting element involved in defense and stress") ~ "Stress responsive",
str_detect(description, "^cis-acting regulatory element involved in seed-specific") ~ "Seed specific",
str_detect(description, "^cis-acting element involved in gibberellin") ~ "GA responsive",
str_detect(description, "^cis-acting element involved in salicylic acid") ~ "SA responsive",
str_detect(description, "^cis-acting regulatory element involved in the MeJA") ~ "JA responsive",
str_detect(description, "^cis-acting element involved in auxin") ~ "Auxin responsive",
str_detect(description, "MYB binding site involved") ~ {
extracted <- str_extract(description, "MYB binding site involved in .+? regulation")
ifelse(is.na(extracted), description, str_replace(extracted, "MYB binding site involved in ", ""))
},
TRUE ~ {
sapply(description, function(desc) {
first_sent <- str_extract(desc, "^[^.]+\\.?")
if (!is.na(first_sent) && nchar(first_sent) > 50) {
words <- str_split(first_sent, " ")[[1]]
if (length(words) >= 6) {
paste(paste(words[1:6], collapse = " "), "...")
} else {
substr(first_sent, 1, 50)
}
} else if (is.na(first_sent)) {
if (nchar(desc) > 40) {
paste0(substr(desc, 1, 40), "...")
} else {
desc
}
} else {
first_sent
}
})
}
),
short_desc = ifelse(nchar(short_desc) > 50,
paste0(substr(short_desc, 1, 50), "..."),
short_desc),
# 基于元件名称的分类(优先)+ 基于description的分类(补充)
category = case_when(
# 1. 核心启动子元件 (最高优先级)
str_detect(element, "(?i)TATA|CAAT|Inr|Y-patch") ~ "Core promoter",
# 2. 激素响应元件
str_detect(element, "(?i)ABRE") ~ "ABA signaling",
str_detect(element, "(?i)P-box|Py-box|GARE") ~ "GA signaling",
str_detect(element, "(?i)CGTCA|TGACG") ~ "JA signaling",
str_detect(element, "(?i)TCA|as-1") ~ "SA signaling",
str_detect(element, "(?i)TGA|AuxRE") ~ "Auxin response",
str_detect(element, "(?i)ERE") ~ "Ethylene response",
# 3. 胁迫响应元件
str_detect(element, "(?i)DRE|CRT|LTR|ARE|STRE|WRE|W.box|W-box") ~ "Stress response",
str_detect(element, "(?i)MBS") & str_detect(description, "(?i)drought|stress") ~ "Stress response",
# 4. 转录因子结合位点
str_detect(element, "(?i)^MYB$|^Myb$|MYB-like|MRE") ~ "MYB-related",
str_detect(element, "(?i)^MYC$|E-box|CANNTG") ~ "MYC-related",
str_detect(element, "(?i)WRKY") ~ "WRKY-related",
# 5. 光响应元件
str_detect(element, "(?i)G-box|G-Box|GT1|I-box|Sp1|Box.4|GATA") ~ "Light response",
# 6. 发育调控元件
str_detect(element, "(?i)RY|AAGAA|CAT|GCN4|Skn-1") ~ "Development",
# 7. 基于description的补充分类
str_detect(description, "(?i)abscisic|ABA") ~ "ABA signaling",
str_detect(description, "(?i)gibberellin|GA") ~ "GA signaling",
str_detect(description, "(?i)jasmonate|MeJA|JA") ~ "JA signaling",
str_detect(description, "(?i)salicylic|SA") ~ "SA signaling",
str_detect(description, "(?i)auxin|Aux") ~ "Auxin response",
str_detect(description, "(?i)ethylene") ~ "Ethylene response",
str_detect(description, "(?i)light|Light") ~ "Light response",
str_detect(description, "(?i)drought|dehydration|low.temperature|stress|anaerobic|cold") ~ "Stress response",
str_detect(description, "(?i)seed.specific|endosperm|seed") ~ "Development",
str_detect(description, "(?i)meristem") ~ "Development",
str_detect(description, "(?i)wound|pathogen|defense") ~ "Stress response",
str_detect(description, "(?i)cell.cycle|DNA.synthesis|replication|cell.division") ~ "Cell cycle",
# 8. 其他
TRUE ~ "Other"
)
)
}
####----函数:智能读取分组文件----####
read_group_file_smart <- function(file_path) {
cat("尝试读取分组文件:", file_path, "\n")
# 读取第一行判断格式
first_line <- readLines(file_path, n = 1)
# 尝试不同的读取方式
tryCatch({
# 方式1: 制表符分隔(这是最可能的)
df <- read.delim(file_path, header = TRUE, sep = "\t", stringsAsFactors = FALSE,
fileEncoding = "UTF-8", quote = "")
if (ncol(df) >= 2) {
# 检查是否有需要的列
if ("label" %in% colnames(df) && "Subgroup" %in% colnames(df)) {
cat("成功以制表符读取,找到label和Subgroup列\n")
return(df)
} else {
# 尝试自动重命名
if (length(colnames(df)) >= 2) {
colnames(df)[1:2] <- c("label", "Subgroup")
cat("自动重命名列为: label, Subgroup\n")
return(df)
}
}
}
}, error = function(e) {
cat("制表符读取失败:", e$message, "\n")
})
tryCatch({
# 方式2: 空格分隔
df <- read.table(file_path, header = TRUE, stringsAsFactors = FALSE,
fileEncoding = "UTF-8")
if (ncol(df) >= 2) {
# 检查是否有需要的列
if ("label" %in% colnames(df) && "Subgroup" %in% colnames(df)) {
cat("成功以空格读取,找到label和Subgroup列\n")
return(df)
} else {
# 尝试自动重命名
if (length(colnames(df)) >= 2) {
colnames(df)[1:2] <- c("label", "Subgroup")
cat("自动重命名列为: label, Subgroup\n")
return(df)
}
}
}
}, error = function(e) {
cat("空格读取失败:", e$message, "\n")
})
# 如果所有方法都失败,尝试读取原始内容并解析
cat("尝试方式3: 读取原始内容并解析\n")
lines <- readLines(file_path, encoding = "UTF-8")
# 删除空行
lines <- lines[lines != ""]
# 解析每一行
parsed_data <- lapply(lines, function(line) {
# 尝试分割:先按制表符,再按多个空格
parts <- str_split(line, "\t")[[1]]
if (length(parts) < 2) {
parts <- str_split(line, "\\s+")[[1]]
}
return(parts)
})
# 提取列名和数据
col_names <- parsed_data[[1]]
data_rows <- parsed_data[-1]
# 创建数据框
df <- as.data.frame(do.call(rbind, data_rows), stringsAsFactors = FALSE)
colnames(df) <- col_names
# 确保有两列
if (ncol(df) >= 2) {
# 重命名为标准列名
if (ncol(df) == 2) {
colnames(df) <- c("label", "Subgroup")
} else {
# 只取前两列
df <- df[, 1:2]
colnames(df) <- c("label", "Subgroup")
cat("只取前两列作为label和Subgroup\n")
}
cat("成功解析文件\n")
return(df)
}
stop("无法解析分组文件,请检查文件格式")
}
####----命令行参数处理----####
args <- commandArgs(trailingOnly = TRUE)
if (length(args) < 1 || args[1] %in% c("-h", "--help")) {
cat("
启动子元件与进化树组合绘图
用法:
Rscript promoter_analysis.R [树文件] [分组文件] [启动子文件] [元件描述文件] [输出前缀]
示例:
Rscript promoter_analysis.R tree.nwk groups.txt promoter.tab elements.csv my_analysis
Rscript promoter_analysis.R tree.nwk groups.txt NA elements.csv my_analysis # 无启动子数据
")
quit(save = "no", status = 0)
}
# 解析命令行参数
cat("=== 解析命令行参数 ===\n")
cat("接收到的参数数量:", length(args), "\n")
# 设置默认值
tree_file <- NULL
group_file <- NULL
promoter_file <- NULL
element_desc_file <- NULL
output_prefix <- "output"
# 根据参数数量分配
if (length(args) >= 1) tree_file <- args[1]
if (length(args) >= 2) group_file <- args[2]
if (length(args) >= 3) promoter_file <- ifelse(args[3] == "NA", NULL, args[3])
if (length(args) >= 4) element_desc_file <- args[4]
if (length(args) >= 5) output_prefix <- args[5]
cat("参数设置:\n")
cat(" 树文件:", ifelse(is.null(tree_file), "未指定", tree_file), "\n")
cat(" 分组文件:", ifelse(is.null(group_file), "未指定", group_file), "\n")
cat(" 启动子文件:", ifelse(is.null(promoter_file), "未指定", promoter_file), "\n")
cat(" 元件描述文件:", ifelse(is.null(element_desc_file), "未指定", element_desc_file), "\n")
cat(" 输出前缀:", output_prefix, "\n\n")
####----检查必需文件----####
# 检查树文件
if (is.null(tree_file) || !file.exists(tree_file)) {
stop("错误: 树文件不存在或未指定。请提供有效的树文件路径。")
}
# 检查分组文件
if (is.null(group_file) || !file.exists(group_file)) {
stop("错误: 分组文件不存在或未指定。请提供有效的分组文件路径。")
}
####----加载数据----####
# 1. 读取进化树文件
cat("=== 步骤1: 读取进化树文件 ===\n")
cat("读取文件:", tree_file, "\n")
tree <- treeio::read.newick(file = tree_file, node.label = "support")
# 清理基因ID
tree_clean <- as_tibble(tree) %>%
dplyr::mutate(
label = str_trim(label),
label = str_remove(label, pattern = "\\.\\d+\\.T\\d+$"),
label = str_remove(label, pattern = "\\.T\\d+$"),
label = str_remove(label, pattern = "\\.\\d+$")
) %>%
as.treedata()
# 2. 读取分组文件(使用智能读取函数)
cat("\n=== 步骤2: 读取分组文件 ===\n")
group_df <- read_group_file_smart(group_file)
# 清理分组文件中的基因ID
group_df <- group_df %>%
mutate(
label = str_trim(label),
label = str_remove(label, pattern = "\\.\\d+\\.T\\d+$"),
label = str_remove(label, pattern = "\\.T\\d+$"),
label = str_remove(label, pattern = "\\.\\d+$"),
Subgroup = str_trim(Subgroup)
)
cat("分组统计:\n")
print(table(group_df$Subgroup))
# 3. 检查基因ID匹配
cat("\n=== 检查基因ID匹配 ===\n")
tree_labels <- tree_clean@phylo$tip.label
group_labels <- group_df$label
common_genes <- intersect(tree_labels, group_labels)
cat("树中基因数量:", length(tree_labels), "\n")
cat("分组文件中基因数量:", length(group_labels), "\n")
cat("匹配的基因数量:", length(common_genes), "\n")
if (length(common_genes) == 0) {
stop("错误: 树和分组文件没有共同的基因ID!")
}
# 只保留匹配的基因
group_df <- group_df %>%
filter(label %in% common_genes)
# 3. 读取启动子元件数据(如果有)
promoter_data_exists <- FALSE
if (!is.null(promoter_file) && file.exists(promoter_file)) {
cat("\n=== 步骤3: 读取启动子元件数据 ===\n")
cat("读取文件:", promoter_file, "\n")
promoter <- tryCatch({
data.table::fread(
file = promoter_file,
header = FALSE,
sep = "\t",
stringsAsFactors = FALSE
)
}, error = function(e) {
cat("读取启动子文件时出错:", e$message, "\n")
return(NULL)
})
if (!is.null(promoter) && ncol(promoter) >= 8) {
promoter <- promoter %>%
dplyr::select(1, 2, 8) %>%
purrr::set_names("X1", "X2", "X3") %>%
dplyr::filter(!str_detect(X2, pattern = "Unname")) %>%
dplyr::filter(!is.na(X2)) %>%
dplyr::filter(!str_detect(X3, "short_function")) %>%
dplyr::mutate(
X1 = str_trim(X1),
X1 = str_remove(string = X1, pattern = "\\.\\d+\\.T\\d+$"),
X1 = str_remove(string = X1, pattern = "\\.T\\d+$"),
X1 = str_remove(string = X1, pattern = "\\.\\d+$")
) %>%
filter(X1 %in% tree_labels)
cat("启动子数据行数:", nrow(promoter), "\n")
if (nrow(promoter) > 0) {
promoter_data_exists <- TRUE
cat("启动子数据可用\n")
} else {
cat("警告: 启动子数据中没有与树匹配的基因\n")
promoter <- NULL
}
} else {
cat("警告: 启动子文件列数不足或读取失败,跳过启动子分析\n")
promoter <- NULL
}
} else {
cat("\n=== 步骤3: 无启动子文件,跳过启动子分析 ===\n")
promoter <- NULL
}
# 4. 读取或生成元件描述文件
if (promoter_data_exists) {
if (!is.null(element_desc_file) && file.exists(element_desc_file)) {
cat("\n=== 步骤4: 读取元件描述文件 ===\n")
cat("读取文件:", element_desc_file, "\n")
# 根据文件扩展名选择读取方式
file_ext <- tools::file_ext(element_desc_file)
if (file_ext == "csv") {
cir_element.desc <- read.csv(element_desc_file, stringsAsFactors = FALSE)
} else if (file_ext %in% c("xls", "xlsx")) {
cir_element.desc <- read_xlsx(element_desc_file)
} else {
cir_element.desc <- read.delim(element_desc_file, stringsAsFactors = FALSE)
}
# 检查列名
if (!"element" %in% names(cir_element.desc)) {
if (any(grepl("element|name|motif", names(cir_element.desc), ignore_case = TRUE))) {
element_col <- names(cir_element.desc)[grepl("element|name|motif", names(cir_element.desc), ignore_case = TRUE)][1]
names(cir_element.desc)[names(cir_element.desc) == element_col] <- "element"
}
}
if (!"description" %in% names(cir_element.desc)) {
if (any(grepl("description|desc|function", names(cir_element.desc), ignore_case = TRUE))) {
desc_col <- names(cir_element.desc)[grepl("description|desc|function", names(cir_element.desc), ignore_case = TRUE)][1]
names(cir_element.desc)[names(cir_element.desc) == desc_col] <- "description"
}
}
# 处理描述,创建简短标签和分类
cir_element.desc <- create_short_descriptions(cir_element.desc)
# 保存处理后的元件描述
write_xlsx(cir_element.desc, paste0(output_prefix, "_element_descriptions.xlsx"))
cat("已保存处理后的元件描述到:", paste0(output_prefix, "_element_descriptions.xlsx"), "\n")
# 合并描述信息到promoter数据
promoter <- promoter %>%
left_join(cir_element.desc, by = c("X2" = "element"))
# 移除没有描述的行
promoter <- promoter[!is.na(promoter$description), ]
if (nrow(promoter) == 0) {
cat("警告: 合并后没有数据,跳过启动子分析\n")
promoter_data_exists <- FALSE
} else {
# 保存合并后的promoter数据用于调试
write.csv(promoter, paste0(output_prefix, "_promoter_merged.csv"), row.names = FALSE)
cat("已保存合并后的启动子数据用于调试\n")
}
} else {
# 没有描述文件,创建简化版本
cat("\n=== 步骤4: 无元件描述文件,使用简化模式 ===\n")
cat("提示: 如需完整功能描述,请使用 scripts/tiqu_miaoshuwenjian.py 从PlantCARE数据库查询\n")
# 从promoter数据中提取唯一元件列表
unique_elements <- unique(promoter$X2)
cat("发现", length(unique_elements), "个唯一元件\n")
# 创建简化描述表
cir_element.desc <- data.frame(
element = unique_elements,
description = unique_elements,
short_desc = unique_elements,
category = "Other",
stringsAsFactors = FALSE
)
# 合并到promoter数据
promoter <- promoter %>%
left_join(cir_element.desc, by = c("X2" = "element"))
# 保存简化描述
write_xlsx(cir_element.desc, paste0(output_prefix, "_element_descriptions_simple.xlsx"))
cat("已保存简化元件描述到:", paste0(output_prefix, "_element_descriptions_simple.xlsx"), "\n")
}
} else if (promoter_data_exists) {
cat("\n警告: 有启动子数据但无元件描述文件,无法进行功能分类\n")
promoter_data_exists <- FALSE
}
####----绘制进化树----####
cat("\n=== 绘制进化树 ===\n")
# 确定亚家族数量和颜色
subgroups <- unique(group_df$Subgroup)
# 定义颜色方案
subgroup_colors <- c(
"I" = "#66c2a5", "II" = "#fc8d62", "III" = "#8da0cb", "IV" = "#e78ac3",
"V" = "#a6d854", "VI" = "#ffd92f", "VII" = "#e5c494", "VIII" = "#b3b3b3"
)
# 为存在的亚家族选择颜色
available_colors <- c()
for (sg in subgroups) {
if (sg %in% names(subgroup_colors)) {
available_colors[sg] <- subgroup_colors[sg]
} else {
available_colors[sg] <- "#999999"
}
}
# 绘制进化树
p.tree <- ggtree(tree_clean, branch.length = "none") %<+% group_df +
geom_tree(aes(color = Subgroup), size = 1.2) +
scale_color_manual(values = available_colors, name = "Subfamily") +
ggnewscale::new_scale_fill() +
geom_nodepoint(
aes(fill = cut(support, c(0, 50, 75, 100))),
shape = 21, size = 3
) +
scale_fill_manual(
values = c("black", "grey", "white"),
guide = "legend",
name = "Bootstrap Percentage",
breaks = c("(75,100]", "(50,75]", "(0,50]"),
labels = c("BP > 75", "50 < BP ≤ 75", "BP ≤ 50")
) +
geom_tiplab(offset = 0.3, size = 3) +
geom_tippoint(aes(x = x + 0.2), shape = 21, fill = "#3690c0", size = 2) +
theme_tree() +
theme(
legend.position = "right",
legend.background = element_rect(color = "#808080", linetype = 1),
legend.box = "vertical",
legend.margin = margin(6, 6, 6, 6)
) +
xlim(NA, 16)
# 保存单独的树图 (PDF + PNG)
tree_output_pdf <- paste0(output_prefix, "_phylogenetic_tree.pdf")
tree_output_png <- paste0(output_prefix, "_phylogenetic_tree.png")
ggsave(filename = tree_output_pdf, plot = p.tree, height = 10, width = 8)
# 使用png()函数直接生成PNG(ggsave在conda环境中有API版本冲突问题)
png(filename = tree_output_png, width = 1200, height = 1500, res = 150)
print(p.tree)
dev.off()
cat("已保存进化树:", tree_output_pdf, "\n")
cat("已保存进化树PNG:", tree_output_png, "\n")
####----绘制启动子热图(如果数据存在)----####
if (promoter_data_exists && nrow(promoter) > 0) {
cat("\n=== 绘制启动子热图 ===\n")
# 获取树中的基因顺序
id_vector <- get_taxa_name(p.tree)
# 筛选元件:移除出现次数太少的元件
element_counts <- promoter %>%
pull(X2) %>%
table() %>%
as_tibble() %>%
purrr::set_names("element", "count") %>%
dplyr::arrange(desc(count))
cat("元件出现次数统计(前20):\n")
print(head(element_counts, 20))
# 保留出现次数>=3的元件
retain_elements <- element_counts %>%
dplyr::filter(count >= 3) %>%
pull(element)
cat(sprintf("保留出现次数>=3的元件: %d个\n", length(retain_elements)))
if (length(retain_elements) == 0) {
cat("警告: 没有元件出现次数>=3,跳过热图绘制\n")
} else {
# 在每个功能类别中,保留出现次数最多的前5个元件
promoter_summary <- promoter %>%
dplyr::filter(X2 %in% retain_elements) %>%
group_by(category, X2) %>%
summarise(count = n(), .groups = 'drop')
promoter_retained <- promoter_summary %>%
group_by(category) %>%
top_n(5, count) %>%
pull(X2) %>%
unique()
cat(sprintf("最终保留元件: %d个\n", length(promoter_retained)))
if (length(promoter_retained) > 0) {
# 准备热图数据 - 关键修复部分
cat("准备热图数据...\n")
# 首先,从promoter数据中提取需要的信息
promoter_filtered <- promoter %>%
dplyr::filter(X2 %in% promoter_retained) %>%
dplyr::select(X1, X2, category, short_desc) %>%
mutate(
X2 = factor(X2, levels = promoter_retained, ordered = TRUE)
)
# 创建所有基因-元件的组合
all_combinations <- expand.grid(
X1 = unique(c(promoter_filtered$X1, id_vector)),
X2 = unique(promoter_filtered$X2),
stringsAsFactors = FALSE
) %>%
mutate(X2 = factor(X2, levels = promoter_retained, ordered = TRUE))
# 汇总每个基因-元件的数量
promoter_counts <- promoter_filtered %>%
group_by(X1, X2, category, short_desc) %>%
summarise(count = n(), .groups = 'drop')
# 合并数据
heatmap_data <- all_combinations %>%
left_join(promoter_counts, by = c("X1", "X2")) %>%
# 从cir_element.desc获取category和short_desc,避免列名冲突
left_join(
cir_element.desc %>%
select(element, category_desc = category, short_desc_element = short_desc),
by = c("X2" = "element")
) %>%
mutate(
count = ifelse(is.na(count), 0, count),
X1 = factor(X1, levels = rev(id_vector), ordered = TRUE),
# 使用promoter数据中的category和short_desc,如果缺失则使用cir_element.desc中的
category = ifelse(is.na(category), category_desc, category),
short_desc = ifelse(is.na(short_desc), short_desc_element, short_desc),
count_cat = cut(
count,
breaks = c(0, 1, 2, 3, 5, 10, 20, 50, 100, 200),
include.lowest = TRUE,
right = FALSE
)
) %>%
select(-category_desc, -short_desc_element) # 删除临时列
# 检查heatmap_data的列
cat("heatmap_data列名:", colnames(heatmap_data), "\n")
cat("short_desc列是否存在:", "short_desc" %in% colnames(heatmap_data), "\n")
cat("category列是否存在:", "category" %in% colnames(heatmap_data), "\n")
cat("heatmap_data行数:", nrow(heatmap_data), "\n")
# 绘制热图
cat("绘制热图...\n")
p.promoter <- ggplot(heatmap_data, aes(x = interaction(X2, short_desc), y = X1)) +
geom_tile(fill = "white", color = "grey80", size = 0.3) +
geom_point(
aes(fill = count_cat),
shape = 22, size = 8
) +
geom_text(aes(label = ifelse(count > 0, count, "")), size = 2.5) +
scale_fill_manual(
values = c(
"[0,1)" = "#f7fbff",
"[1,2)" = "#c6dbef",
"[2,3)" = "#9ecae1",
"[3,5)" = "#6baed6",
"[5,10)" = "#4292c6",
"[10,20)" = "#2171b5",
"[20,50)" = "#08519c",
"[50,100)" = "#08306b",
"[100,200]" = "#041d3a"
),
name = "元件数量",
drop = FALSE
) +
guides(x = "axis_nested") +
labs(x = "", y = "") +
theme_bw() +
theme(
axis.text.x = element_text(
color = "black",
angle = 90,
size = 8,
hjust = 1,
vjust = 0.5
),
axis.text.y = element_text(color = "black", size = 9),
legend.position = "right",
legend.background = element_rect(color = "#808080", linetype = 1),
axis.ticks.length.x.top = unit(0.2, "cm"),
axis.ticks.y = element_blank(),
panel.border = element_blank(),
panel.grid = element_blank(),
plot.margin = unit(c(0.3, 0.3, 0.3, 0.3), "cm")
) +
scale_x_discrete(expand = c(0, 0), position = "top") +
guides(fill = guide_legend(ncol = 1))
# 保存热图 (PDF + PNG) - 单独热图保留
heatmap_output_pdf <- paste0(output_prefix, "_promoter_heatmap.pdf")
heatmap_output_png <- paste0(output_prefix, "_promoter_heatmap.png")
ggsave(filename = heatmap_output_pdf, plot = p.promoter, height = 12, width = 14)
png(filename = heatmap_output_png, width = 2100, height = 1800, res = 150)
print(p.promoter)
dev.off()
cat("已保存启动子热图:", heatmap_output_pdf, "\n")
cat("已保存启动子热图PNG:", heatmap_output_png, "\n")
####----新版组合图:仿Motif风格(三面板布局)----####
cat("\n=== 绘制新版组合图(仿Motif风格)===\n")
# 准备亚家族颜色(与Motif一致使用Dark2)
subfam_levels <- sort(unique(group_df$Subgroup))
n_subfam <- length(subfam_levels)
base_cols_sf <- brewer.pal(8, "Dark2")
subfam_cols_vec <- rep_len(base_cols_sf, n_subfam)
subfam_cols <- setNames(subfam_cols_vec, subfam_levels)
# 从p.tree对象中提取树的坐标数据
tree_dat <- p.tree$data
tip_dat <- tree_dat %>%
filter(isTip) %>%
select(label, x, y) %>%
left_join(group_df %>% select(label, Subgroup), by = "label")
# 计算亚家族范围(Y轴范围)
subfam_ranges <- tip_dat %>%
filter(!is.na(Subgroup)) %>%
group_by(Subgroup) %>%
summarise(
ymin = min(y),
ymax = max(y),
ymid = (ymin + ymax) / 2,
.groups = 'drop'
)
# 获取树的基本坐标
x_tree_min <- min(tree_dat$x, na.rm = TRUE)
x_tip_max <- max(tip_dat$x, na.rm = TRUE)
max_y <- max(tree_dat$y, na.rm = TRUE)
tree_core_width <- x_tip_max - x_tree_min
# 计算三个面板的X坐标(仿Motif风格)
label_offset <- 0.4
x_label_left <- x_tip_max + label_offset
label_panel_width <- max(1.5, tree_core_width * 0.5)
# Panel A: 树 + gene ID
x_A_left <- x_tree_min
x_A_right <- x_label_left + label_panel_width
# Panel B: 亚家族竖线(中间)
gap1 <- max(0.3, tree_core_width * 0.1)
r_B <- 0.4
x_B_left <- x_A_right + gap1
width_B <- tree_core_width * r_B
x_B_right <- x_B_left + width_B
x_subfam <- (x_B_left + x_B_right) / 2
# Panel C: 启动子热图区域(右侧)
gap2 <- max(0.3, tree_core_width * 0.1)
r_C <- 1.6
x_C_left <- x_B_right + gap2
width_C <- tree_core_width * r_C
x_C_right <- x_C_left + width_C
# 准备热图数据 - 将热图转换为矩形数据
# 为每个基因-元件组合创建矩形
heatmap_rect <- heatmap_data %>%
mutate(
# Y坐标保持与树一致
ymin = as.numeric(X1) - 0.4,
ymax = as.numeric(X1) + 0.4,
# X坐标:将元件位置映射到Panel C
# 计算每个元件在热图中的顺序位置
element_index = as.numeric(factor(interaction(X2, short_desc))),
n_elements = n_distinct(interaction(X2, short_desc)),
# 每个元件占据的宽度
elem_width = width_C / n_elements,
xmin = x_C_left + (element_index - 1) * elem_width,
xmax = x_C_left + element_index * elem_width
)
# 为不同数量级别设置颜色
count_colors <- c(
"[0,1)" = "#f7fbff",
"[1,2)" = "#c6dbef",
"[2,3)" = "#9ecae1",
"[3,5)" = "#6baed6",
"[5,10)" = "#4292c6",
"[10,20)" = "#2171b5",
"[20,50)" = "#08519c",
"[50,100)" = "#08306b",
"[100,200]" = "#041d3a"
)
# 构建组合图(仿Motif风格)
p_combo <- p.tree +
# Panel B: 亚家族竖线
geom_segment(
data = subfam_ranges,
inherit.aes = FALSE,
aes(x = x_subfam, xend = x_subfam, y = ymin, yend = ymax, colour = Subgroup),
linewidth = 1.5
) +
geom_text(
data = subfam_ranges,
inherit.aes = FALSE,
aes(x = x_subfam + width_B * 0.08, y = ymid, label = Subgroup, colour = Subgroup),
hjust = 0, size = 3.5
) +
scale_color_manual(values = subfam_cols, name = "Subfamily") +
# Panel C: 启动子元件矩形(使用new_scale_fill)
ggnewscale::new_scale_fill() +
geom_rect(
data = heatmap_rect %>% filter(count > 0),
inherit.aes = FALSE,
aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = count_cat),
color = "grey80", size = 0.2
) +
geom_text(
data = heatmap_rect %>% filter(count > 0),
inherit.aes = FALSE,
aes(x = (xmin + xmax) / 2, y = (ymin + ymax) / 2, label = count),
size = 2, color = "white"
) +
scale_fill_manual(values = count_colors, name = "Element Count", drop = FALSE) +
# 设置坐标范围
coord_cartesian(xlim = c(x_A_left, x_C_right + tree_core_width * 0.1)) +
expand_limits(y = max_y + 1.5) +
# 添加表头
annotate("text", x = (x_A_left + x_A_right) / 2, y = max_y + 1.2,
label = "Phylogenetic tree", fontface = "bold", size = 4) +
annotate("text", x = x_subfam, y = max_y + 1.2,
label = "Subfamily", fontface = "bold", size = 4) +
annotate("text", x = (x_C_left + x_C_right) / 2, y = max_y + 1.2,
label = "Promoter Elements", fontface = "bold", size = 4) +
theme(
legend.position = "right",
legend.background = element_rect(color = "#969696", fill = "#d9d9d9")
)
# 保存新版组合图
combined_new_pdf <- paste0(output_prefix, "_tree_promoter_combo_new.pdf")
combined_new_png <- paste0(output_prefix, "_tree_promoter_combo_new.png")
ggsave(filename = combined_new_pdf, plot = p_combo, width = 16, height = 14)
png(filename = combined_new_png, width = 4800, height = 4200, res = 300)
print(p_combo)
dev.off()
cat("已保存新版组合图(仿Motif风格):", combined_new_pdf, "\n")
cat("已保存新版组合图PNG:", combined_new_png, "\n")
# 保留旧版组合图生成代码(兼容)
####----旧版组合图(可选)----####
cat("\n=== 同时生成旧版组合图 ===\n")
p.promoter2 <- p.promoter + theme(axis.text.y = element_blank())
combined_plot <- p.promoter2 %>% insert_left(p.tree, width = 0.7)
combined_output_pdf <- paste0(output_prefix, "_tree_promoter_combined.pdf")
combined_output_png <- paste0(output_prefix, "_tree_promoter_combined.png")
ggsave(filename = combined_output_pdf, plot = combined_plot, height = 14, width = 16)
png(filename = combined_output_png, width = 2400, height = 2100, res = 150)
print(combined_plot)
dev.off()
cat("已保存旧版组合图:", combined_output_pdf, "\n")
} else {
cat("\n=== 跳过启动子热图绘制(保留元件不足) ===\n")
}
}
} else {
cat("\n=== 跳过启动子相关绘图(无数据或数据不足) ===\n")
}
####----输出统计信息----####
cat("\n===== 分析完成 =====\n")
cat("生成的文件:\n")
cat("1.", tree_output_pdf, "\n")
cat(" ", tree_output_png, "\n")
if (exists("heatmap_output_pdf") && file.exists(heatmap_output_pdf)) {
cat("2.", heatmap_output_pdf, "\n")
cat(" ", heatmap_output_png, "\n")
}
if (exists("combined_output_pdf") && file.exists(combined_output_pdf)) {
cat("3.", combined_output_pdf, "\n")
cat(" ", combined_output_png, "\n")
}
if (exists("combined_new_pdf") && file.exists(combined_new_pdf)) {
cat("3a. 新版组合图(仿Motif风格):", combined_new_pdf, "\n")
cat(" ", combined_new_png, "\n")
}
if (exists("final_output_pdf") && file.exists(final_output_pdf)) {
cat("4.", final_output_pdf, "\n")
cat(" ", final_output_png, "\n")
}
# 保存分组统计
group_summary <- group_df %>%
group_by(Subgroup) %>%
summarise(Genes = n(), .groups = 'drop')
group_summary_file <- paste0(output_prefix, "_subgroup_summary.xlsx")
write_xlsx(group_summary, group_summary_file)
cat("5.", group_summary_file, "\n")
# 保存参数记录
params <- data.frame(
参数 = c("树文件", "分组文件", "启动子文件", "元件描述文件", "输出前缀"),
值 = c(tree_file, group_file,
ifelse(is.null(promoter_file), "未提供", promoter_file),
ifelse(is.null(element_desc_file), "未提供", element_desc_file),
output_prefix)
)
params_file <- paste0(output_prefix, "_analysis_parameters.csv")
write.csv(params, params_file, row.names = FALSE)
cat("6.", params_file, "\n")
cat("\n===== 分析完成 =====\n")