Files
PlantAI-rag-new/bioflow_kernel/scripts/promoter_analysis_complete.R

901 lines
33 KiB
R
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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")