PGAP2 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. pgap2/__init__.py +4 -0
  2. pgap2/dependencies/draw_post_profile.r +193 -0
  3. pgap2/dependencies/draw_post_stat.r +97 -0
  4. pgap2/dependencies/draw_prep.r +159 -0
  5. pgap2/dependencies/maskrc-svg.py +236 -0
  6. pgap2/dependencies/run_fastbaps +55 -0
  7. pgap2/lib/basic.py +203 -0
  8. pgap2/lib/panclust.py +24 -0
  9. pgap2/lib/pangenome.py +343 -0
  10. pgap2/lib/pangp.py +121 -0
  11. pgap2/lib/phylogeny.py +596 -0
  12. pgap2/lib/pklcheck.py +80 -0
  13. pgap2/lib/species.py +160 -0
  14. pgap2/lib/strain.py +7 -0
  15. pgap2/lib/tree.py +210 -0
  16. pgap2/main.py +33 -0
  17. pgap2/pgap2/__init__.py +2 -0
  18. pgap2/pgap2/dependencies/maskrc-svg.py +236 -0
  19. pgap2/pgap2/lib/basic.py +203 -0
  20. pgap2/pgap2/lib/panclust.py +24 -0
  21. pgap2/pgap2/lib/pangenome.py +343 -0
  22. pgap2/pgap2/lib/pangp.py +121 -0
  23. pgap2/pgap2/lib/phylogeny.py +596 -0
  24. pgap2/pgap2/lib/pklcheck.py +80 -0
  25. pgap2/pgap2/lib/species.py +160 -0
  26. pgap2/pgap2/lib/strain.py +7 -0
  27. pgap2/pgap2/lib/tree.py +210 -0
  28. pgap2/pgap2/main.py +33 -0
  29. pgap2/pgap2/postprocess/baps.py +144 -0
  30. pgap2/pgap2/postprocess/profile.py +122 -0
  31. pgap2/pgap2/postprocess/singletree.py +130 -0
  32. pgap2/pgap2/postprocess/stat.py +511 -0
  33. pgap2/pgap2/postprocess/tajimas_d.py +124 -0
  34. pgap2/pgap2/utils/__init__.py +0 -0
  35. pgap2/pgap2/utils/alignment.py +45 -0
  36. pgap2/pgap2/utils/arrangement_detector.py +122 -0
  37. pgap2/pgap2/utils/data_loader.py +679 -0
  38. pgap2/pgap2/utils/draw.py +1095 -0
  39. pgap2/pgap2/utils/gene_retriever.py +212 -0
  40. pgap2/pgap2/utils/generate_tree.py +310 -0
  41. pgap2/pgap2/utils/partition.py +851 -0
  42. pgap2/pgap2/utils/postprocess.py +18 -0
  43. pgap2/pgap2/utils/preprocess.py +475 -0
  44. pgap2/pgap2/utils/supply.py +139 -0
  45. pgap2/pgap2/utils/tools.py +607 -0
  46. pgap2/postprocess/baps.py +144 -0
  47. pgap2/postprocess/profile.py +122 -0
  48. pgap2/postprocess/singletree.py +130 -0
  49. pgap2/postprocess/stat.py +511 -0
  50. pgap2/postprocess/tajimas_d.py +123 -0
  51. pgap2/toy_input/287_91.fa +1 -0
  52. pgap2/toy_input/287_91.gff +7657 -0
  53. pgap2/toy_input/AKU_12601.gff +1 -0
  54. pgap2/toy_input/Readme_input.md +3 -0
  55. pgap2/toy_input/SC_B67.gbk +1 -0
  56. pgap2/utils/__init__.py +0 -0
  57. pgap2/utils/alignment.py +45 -0
  58. pgap2/utils/arrangement_detector.py +122 -0
  59. pgap2/utils/data_loader.py +684 -0
  60. pgap2/utils/draw.py +1095 -0
  61. pgap2/utils/gene_retriever.py +212 -0
  62. pgap2/utils/generate_tree.py +310 -0
  63. pgap2/utils/partition.py +851 -0
  64. pgap2/utils/postprocess.py +18 -0
  65. pgap2/utils/preprocess.py +475 -0
  66. pgap2/utils/supply.py +138 -0
  67. pgap2/utils/tools.py +615 -0
  68. pgap2-0.1.2.dist-info/METADATA +154 -0
  69. pgap2-0.1.2.dist-info/RECORD +73 -0
  70. pgap2-0.1.2.dist-info/WHEEL +5 -0
  71. pgap2-0.1.2.dist-info/entry_points.txt +2 -0
  72. pgap2-0.1.2.dist-info/licenses/LICENSE +21 -0
  73. pgap2-0.1.2.dist-info/top_level.txt +1 -0
pgap2/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ import importlib.metadata
2
+
3
+ __version__ = importlib.metadata.version("pgap2")
4
+ __author__ = 'Congfan Bu'
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env Rscript
2
+ # install.packages(c("ggplot2", "dplyr", "optparse", "readr", "scales", "patchwork"))
3
+ library(ggpubr)
4
+
5
+ library(dplyr)
6
+ library(optparse)
7
+ library(patchwork)
8
+
9
+ # 设置命令行参数解析
10
+ option_list <- list(
11
+ make_option(c("-a", "--pan_group_stat"), type = "character", help = "postprocess.pan_group_stat.tsv"),
12
+ make_option(c("-b", "--clust_strain_freq"), type = "character", help = "postprocess.clust_strain_freq.tsv"),
13
+ make_option(c("-c", "--rarefaction"), type = "character", help = "postprocess.rarefaction.tsv"),
14
+ make_option(c("-d", "--new_clusters"), type = "character", help = "postprocess.new_clusters.tsv"),
15
+ make_option(c("-e", "--para_stat"), type = "character", help = "postprocess.para_stat.tsv"),
16
+ make_option(c("-s", "--single_file"), type = "logical", action = "store_true", default = FALSE, help = "Generate each plot to the single file"),
17
+ make_option(c("-o", "--output_dir"), type = "character", help = "Output directory")
18
+ )
19
+
20
+ # 解析命令行参数
21
+ opt_parser <- OptionParser(option_list = option_list)
22
+ opt <- parse_args(opt_parser)
23
+
24
+ # 创建输出目录
25
+ if (!dir.exists(opt$output_dir)) {
26
+ dir.create(opt$output_dir)
27
+ }
28
+
29
+ # 读取饼图数据文件
30
+
31
+ pan_group_stat_data <- read.csv(opt$pan_group_stat, header = TRUE, sep = "\t")
32
+
33
+ clust_strain_freq_data <- read.csv(opt$clust_strain_freq, header = TRUE, sep = "\t")
34
+
35
+ rarefaction_data <- read.csv(opt$rarefaction, header = TRUE, sep = "\t")
36
+
37
+ new_clusters_data <- read.csv(opt$new_clusters, header = TRUE, sep = "\t")
38
+
39
+ para_stat_data <- read.csv(opt$para_stat, header = TRUE, sep = "\t")
40
+
41
+ #-----------------------------------------------------------------------------#
42
+ # 保存图形
43
+ save_para_plots <- function(A, B, single_file, output_dir) {
44
+ if (single_file) {
45
+ ggsave(file.path(output_dir, "postprocess.para_stat.pdf"), A + ggtitle(""))
46
+ ggsave(file.path(output_dir, "postprocess.para_stat_facet.pdf"), B + ggtitle(""))
47
+ } else {
48
+ combined_plot <- A + B +
49
+ plot_layout(
50
+ ncol = 2, # 2列
51
+ nrow = 1, # 2行
52
+ widths = c(1, 1), # 第一列宽度为第二列的两倍
53
+ heights = c(1, 1) # 两行的高度相等
54
+ )
55
+ ggsave(file.path(output_dir, "pgap2.postprocess_stat_para.pdf"), combined_plot, width = 10.52, height = 5.8)
56
+ }
57
+ }
58
+
59
+ save_basic_plots <- function(A, B, C, D, single_file, output_dir) {
60
+ if (single_file) {
61
+ ggsave(file.path(output_dir, "postprocess.pan_group_stat.pdf"), A)
62
+ ggsave(file.path(output_dir, "postprocess.clust_strain_freq.pdf"), B)
63
+ ggsave(file.path(output_dir, "postprocess.rarefaction.pdf"), C)
64
+ ggsave(file.path(output_dir, "postprocess.new_clusters.pdf"), D)
65
+ } else {
66
+ combined_plot <- A + B + C + D +
67
+ plot_layout(
68
+ ncol = 2, # 2列
69
+ nrow = 2, # 2行
70
+ widths = c(1, 1), # 第一列宽度为第二列的两倍
71
+ heights = c(1, 1) # 两行的高度相等
72
+ )
73
+ ggsave(file.path(output_dir, "pgap2.postprocess_profile.pdf"), combined_plot, width = 9.52, height = 7.25)
74
+ }
75
+ }
76
+
77
+ draw_pan_group_stat <- function(pan_group_stat_data) {
78
+ labs <- paste0(pan_group_stat_data$Group, ": ", pan_group_stat_data$Count, " (", pan_group_stat_data$Proportion, "%)")
79
+
80
+ pan_group_stat <- ggpie(pan_group_stat_data, "Count",
81
+ label = labs,
82
+ fill = "Group", color = "white", lab.pos = "in", size = 1,
83
+ palette = c("#B8DBB3", "#72B063", "#719AAC", "#E29135", "#94C6CD"),
84
+ lab.font = c(4, "plain", "black"),
85
+ ) + theme(
86
+ legend.position = "none",
87
+ plot.margin = margin(0, 0, 0, 0), # 去除空白边距
88
+ axis.text = element_blank(), # 隐藏坐标轴
89
+ axis.title = element_blank()
90
+ ) + ggtitle("A")
91
+ return(pan_group_stat)
92
+ }
93
+
94
+ draw_clust_strain_freq <- function(clust_strain_freq_data) {
95
+ clust_strain_freq <- gghistogram(clust_strain_freq_data,
96
+ x = "Freq", bins = 50, fill = "#B8DBB3",
97
+ xlab = "Genome Frequency", ylab = "Gene Cluster Number"
98
+ ) + scale_y_log10() + ggtitle("B")
99
+ return(clust_strain_freq)
100
+ }
101
+
102
+
103
+ draw_rarefaction <- function(rarefaction_data) {
104
+ rarefaction <- ggscatter(rarefaction_data,
105
+ x = "Strain", y = "Sampling", color = "Type", fill = NA, alpha = 0.5, size = 1, palette = c("#72B063", "#719AAC"),
106
+ xlab = "Genome Number", ylab = "Gene Cluster Number"
107
+ ) + theme(
108
+ axis.line = element_line(linewidth = 0),
109
+ panel.background = element_blank(),
110
+ panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5), # 添加边框
111
+ panel.grid = element_blank(), # 去除网格线
112
+ plot.margin = margin(10, 10, 10, 10), # 增加图形的外边距,防止文字被裁剪
113
+ legend.position = c(0.15, 0.85), # 将图例放到图框内
114
+ legend.box = "horizontal", # 使图例在框内水平排列
115
+ legend.background = element_blank(), # 去除图例背景
116
+ legend.key.size = unit(0.5, "cm"), # 控制图例块的大小
117
+ legend.title = element_text(size = 10), # 设置图例标题字体大小
118
+ legend.text = element_text(size = 8) # 设置图例文本字体大小
119
+ ) + guides(color = guide_legend(override.aes = list(alpha = 1))) + ggtitle("C")
120
+ # ggboxplot(rarefaction.data, color = 'Type', x = 'Strain', y = 'Sampling',fill=NA) +
121
+ # theme(
122
+ # axis.line = element_line(linewidth = 0),
123
+ # panel.border = element_rect(color = "black", fill = NA, linewidth = 0.75), # 添加边框
124
+ # panel.grid = element_blank(), # 去除网格线
125
+ # plot.margin = margin(10, 10, 10, 10) # 增加图形的外边距,防止文字被裁剪
126
+ # )+
127
+ # scale_x_discrete(breaks = unique(rarefaction.data$Strain)[seq(1, length(unique(rarefaction.data$Strain)), by = 5)])
128
+
129
+ return(rarefaction)
130
+ }
131
+
132
+ draw_new_clusters <- function(new_clusters_data) {
133
+ new_clusters <- ggbarplot(new_clusters_data,
134
+ x = "Strain", y = "Sampling", add = "mean_se", fill = "#94C6CD",
135
+ xlab = "Genome Number", ylab = "New Gene Cluster Number"
136
+ ) + theme(
137
+ axis.line = element_line(linewidth = 0),
138
+ panel.background = element_blank(),
139
+ panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5), # 添加边框
140
+ panel.grid = element_blank(), # 去除网格线
141
+ plot.margin = margin(10, 10, 10, 10), # 增加图形的外边距,防止文字被裁剪
142
+ legend.box = "horizontal", # 使图例在框内水平排列
143
+ legend.key.size = unit(0.5, "cm"), # 控制图例块的大小
144
+ legend.title = element_text(size = 10), # 设置图例标题字体大小
145
+ legend.text = element_text(size = 8) # 设置图例文本字体大小
146
+ ) + guides(color = guide_legend(override.aes = list(alpha = 1))) + ggtitle("D")
147
+ return(new_clusters)
148
+ }
149
+
150
+ draw_para_stat <- function(para_stat_data) {
151
+ para_stat_data$duplication_degree <- log(para_stat_data$Para_gene / para_stat_data$Para_strain)
152
+ para_stat_a <- ggscatter(para_stat_data,
153
+ x = "Para_strain", y = "Para_gene", size = "duplication_degree",
154
+ color = "black", shape = 21, fill = "Group",
155
+ conf.int = TRUE,
156
+ palette = c("#B8DBB3", "#72B063", "#719AAC", "#E29135", "#94C6CD"),
157
+ xlab = "Paralogous Strain",
158
+ ylab = "Paralogous Gene"
159
+ ) +
160
+ geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "red") + # 添加 x = y 线
161
+ guides(
162
+ size = "none" # 隐藏 size 图例
163
+ ) + ggtitle("A")
164
+ return(para_stat_a)
165
+ }
166
+
167
+ draw_para_stat_facet <- function(para_stat_data) {
168
+ para_stat_data$duplication_degree <- log(para_stat_data$Para_gene / para_stat_data$Para_strain)
169
+ para_stat_b <- ggscatter(para_stat_data,
170
+ x = "Para_strain", y = "Para_gene", size = "duplication_degree", color = "duplication_degree",
171
+ conf.int = TRUE, facet.by = "Group",
172
+ xlab = "Paralogous Strain",
173
+ ylab = "Paralogous Gene"
174
+ ) +
175
+ geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "red") + # 添加 x = y 线
176
+ scale_y_log10() + scale_x_log10() + # 对数缩放
177
+ scale_color_gradient(low = "#dddfe6", high = "#d90429") +
178
+ labs(color = "Dup degree") +
179
+ guides(
180
+ size = "none" # 隐藏 size 图例
181
+ ) + ggtitle("B")
182
+ return(para_stat_b)
183
+ }
184
+
185
+ A <- draw_pan_group_stat(pan_group_stat_data)
186
+ B <- draw_clust_strain_freq(clust_strain_freq_data)
187
+ C <- draw_rarefaction(rarefaction_data)
188
+ D <- draw_new_clusters(new_clusters_data)
189
+ E <- draw_para_stat(para_stat_data)
190
+ F <- draw_para_stat_facet(para_stat_data)
191
+
192
+ save_basic_plots(A, B, C, D, opt$single_file, opt$output_dir)
193
+ save_para_plots(E, F, opt$single_file, opt$output_dir)
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env Rscript
2
+ library(ggpubr)
3
+
4
+ library(dplyr)
5
+ library(optparse)
6
+ library(patchwork)
7
+
8
+ # 设置命令行参数解析
9
+ option_list <- list(
10
+ make_option(c("-a", "--stat_attrs"), type = "character", help = "postprocess.stat_attrs.tsv"),
11
+ make_option(c("-s", "--single_file"), action = "store_true", default = FALSE, help = "Generate each plot to the single file"),
12
+ make_option(c("-o", "--output_dir"), type = "character", help = "Output directory")
13
+ )
14
+
15
+ # 解析命令行参数
16
+ opt_parser <- OptionParser(option_list = option_list)
17
+ opt <- parse_args(opt_parser)
18
+
19
+ # 创建输出目录
20
+ if (!dir.exists(opt$output_dir)) {
21
+ dir.create(opt$output_dir)
22
+ }
23
+
24
+ stat_attrs_data <- read.csv(opt$stat_attrs, header = TRUE, sep = "\t")
25
+
26
+
27
+ #-----------------------------------------------------------------------------#
28
+ # 保存图形
29
+
30
+ save_basic_plots <- function(A, B, C, D, single_file, output_dir) {
31
+ if (single_file) {
32
+ ggsave(file.path(output_dir, "postprocess.stat_attrs_mean.pdf"), A)
33
+ ggsave(file.path(output_dir, "postprocess.stat_attrs_min.pdf"), B)
34
+ ggsave(file.path(output_dir, "postprocess.stat_attrs_var.pdf"), C)
35
+ ggsave(file.path(output_dir, "postprocess.stat_attrs_uni.pdf"), D)
36
+ } else {
37
+ combined_plot <- A + B + C + D +
38
+ plot_layout(
39
+ guides = "collect",
40
+ ncol = 2, # 2列
41
+ nrow = 2, # 2行
42
+ widths = c(1, 1), # 第一列宽度为第二列的两倍
43
+ heights = c(1, 1)
44
+ ) +
45
+ theme(legend.position = "bottom")
46
+ ggsave(file.path(output_dir, "pgap2.postprocess_stat.pdf"), combined_plot, width = 8.6, height = 7.4)
47
+ }
48
+ }
49
+
50
+ draw_stat_attr <- function(stat_attrs_data, attr, xlab_name) {
51
+ if (attr == "mean" || attr == "min") {
52
+ left_position <- 0.15
53
+ } else {
54
+ left_position <- 0.85
55
+ }
56
+
57
+
58
+ attr_plot <- ggline(subset(stat_attrs_data, Attr == attr),
59
+ x = "Edge", y = "Prop",
60
+ color = "Group", scales = "free", size = 1,
61
+ xlab = xlab_name, ylab = "Gene Cluster Proportion",
62
+ palette = c("#B8DBB3", "#72B063", "#719AAC", "#E29135", "#94C6CD")
63
+ ) +
64
+ scale_y_log10() +
65
+ theme(
66
+ axis.text.x = element_text(angle = 45, hjust = 1),
67
+ axis.line = element_line(linewidth = 0),
68
+ panel.background = element_blank(),
69
+ panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5), # 添加边框
70
+ panel.grid = element_blank(), # 去除网格线
71
+ plot.margin = margin(10, 10, 10, 10), # 增加图形的外边距,防止文字被裁剪
72
+ legend.box = "vertical", # 使图例在框内水平排列
73
+ legend.position = c(left_position, 0.85),
74
+ legend.background = element_blank(),
75
+ legend.direction = "vertical",
76
+ legend.key.size = unit(0.5, "cm"), # 控制图例块的大小
77
+ legend.title = element_text(size = 10), # 设置图例标题字体大小
78
+ legend.text = element_text(size = 8)
79
+ ) +
80
+ guides(color = guide_legend(override.aes = list(alpha = 1))) + ggtitle(toupper(attr))
81
+
82
+ return(attr_plot)
83
+ }
84
+
85
+
86
+ stat_attrs_data <- stat_attrs_data %>%
87
+ group_by(Attr, Group) %>% # 按 Attr 分组
88
+ mutate(Prop = Count / sum(Count)) %>% # 计算每行的 Prop 值
89
+ ungroup() # 取消分组
90
+ stat_attrs_data$Group <- factor(stat_attrs_data$Group, levels = c("Strict_core", "Core", "Soft_core", "Shell", "Cloud"))
91
+
92
+ A <- draw_stat_attr(stat_attrs_data, "mean", xlab_name = "Gene Identity")
93
+ B <- draw_stat_attr(stat_attrs_data, "min", xlab_name = "Gene Identity")
94
+ C <- draw_stat_attr(stat_attrs_data, "var", xlab_name = "Gene Cluster Variance")
95
+ D <- draw_stat_attr(stat_attrs_data, "uni", xlab_name = "Gene Identity")
96
+
97
+ save_basic_plots(A, B, C, D, opt$single_file, opt$output_dir)
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env Rscript
2
+
3
+ library(ggpubr)
4
+ library(ggrepel)
5
+
6
+ library(dplyr)
7
+ library(tidyr)
8
+ library(optparse)
9
+ library(patchwork)
10
+
11
+ # 设置命令行参数解析
12
+ option_list <- list(
13
+ make_option(c("-a", "--stat_attrs"), type = "character", help = "preprocess.stat.tsv"),
14
+ make_option(c("-b", "--gene_code"), type = "character", help = "preprocess.gene_code.csv"),
15
+ make_option(c("-c", "--ani_thre"), type = "character", help = "ANI threshold"),
16
+ make_option(c("-s", "--single_file"), action = "store_true", default = FALSE, help = "Generate each plot to the single file"),
17
+ make_option(c("-o", "--output_dir"), type = "character", help = "Output directory")
18
+ )
19
+
20
+ # 解析命令行参数
21
+ opt_parser <- OptionParser(option_list = option_list)
22
+ opt <- parse_args(opt_parser)
23
+
24
+ # 创建输出目录
25
+ if (!dir.exists(opt$output_dir)) {
26
+ dir.create(opt$output_dir)
27
+ }
28
+
29
+ input_prep <- read.csv(opt$stat_attrs, header = TRUE, sep = "\t")
30
+ input_code <- read.csv(opt$gene_code, header = TRUE, sep = ",", row.names = 1)
31
+
32
+
33
+ #-----------------------------------------------------------------------------#
34
+ # 保存图形
35
+
36
+ save_basic_plots <- function(A, B, C, D, E, single_file, output_dir) {
37
+ if (single_file) {
38
+ ggsave(file.path(output_dir, "preprocess.ANI.pdf"), A)
39
+ ggsave(file.path(output_dir, "preprocess.gene_number.pdf"), B)
40
+ ggsave(file.path(output_dir, "preprocess.half_core.pdf"), C)
41
+ ggsave(file.path(output_dir, "preprocess.proportion.pdf"), D)
42
+ ggsave(file.path(output_dir, "preprocess.gene_code.pdf"), E)
43
+ } else {
44
+ p1 <- A + ggtitle("A") +
45
+ B + ggtitle("B") +
46
+ C + ggtitle("C") +
47
+ plot_layout(guides = "collect", ncol = 3)
48
+ p2 <- D + ggtitle("D") +
49
+ E + ggtitle("E") +
50
+ plot_layout(guides = "collect", ncol = 2) & # 合并图例
51
+ theme(legend.position = "right")
52
+ combined_plot <- p1 / p2 + plot_layout(heights = c(1, 2))
53
+ ggsave(file.path(output_dir, "pgap2.preprocess.pdf"), combined_plot, width = 9.8, height = 8.4)
54
+ }
55
+ }
56
+
57
+ draw_ani <- function(input_prep, ani_thre) {
58
+ p <- ggviolin(input_prep,
59
+ y = "ani", add = "jitter",
60
+ add.params = list(size = 3, color = "black", fill = "#B8DBB3", shape = 21),
61
+ xlab = "Genome", ylab = "ANI"
62
+ ) +
63
+ geom_hline(yintercept = ani_thre, linetype = "dotted", col = "red") +
64
+ theme(legend.position = "none") +
65
+ scale_x_discrete(labels = c("")) +
66
+ # 使用 geom_text_repel() 添加标签和箭头
67
+ geom_text_repel(
68
+ data = subset(input_prep, ani == 100 | is_outlier_ani == 1), # 筛选满足条件的数据
69
+ aes(x = as.factor(1), y = ani, label = strain), # 标签位置与数据
70
+ arrow = arrow(type = "closed", length = unit(0.1, "inches")), # 添加箭头
71
+ vjust = -0.5, size = 3, color = "black", # 标签和箭头的样式
72
+ box.padding = 0.2, min.segment.length = 1 # 调整标签与点的间距
73
+ )
74
+ return(p)
75
+ }
76
+
77
+ draw_gene_number <- function(input_prep) {
78
+ p <- ggviolin(input_prep,
79
+ y = "total_gene_num", add = "jitter", xlab = "Genome", ylab = "Number of genes",
80
+ add.params = list(size = 3, color = "black", fill = "#B8DBB3", shape = 21)
81
+ ) + theme(legend.position = "None") + scale_x_discrete(labels = c(""))
82
+ return(p)
83
+ }
84
+
85
+ draw_half_core <- function(input_prep) {
86
+ p <- ggscatter(input_prep,
87
+ x = "half_core", y = "single_cloud",
88
+ size = "total_gene_num", color = "total_gene_num", xlab = "Half core", ylab = "Single cloud"
89
+ ) +
90
+ guides(size = "none", color = guide_colorbar(barheight = 5)) +
91
+ scale_color_gradient(high = "#990033", low = "white") +
92
+ theme(legend.position = "right") +
93
+ labs(color = "Count")
94
+ return(p)
95
+ }
96
+
97
+ draw_proportion <- function(input_prep) {
98
+ input_prep$others <- input_prep$total_gene_num - input_prep$half_core - input_prep$single_cloud
99
+ used_prep <- input_prep %>% select(strain, total_gene_num, half_core, single_cloud, others)
100
+ # Transform the data into a long format
101
+ long_data <- used_prep %>%
102
+ pivot_longer(cols = c(half_core, single_cloud, others), names_to = "Category", values_to = "Count") %>%
103
+ mutate(
104
+ Category = factor(Category, levels = c("single_cloud", "others", "half_core")), # Reorder the categories
105
+ strain = factor(strain, levels = used_prep$strain)
106
+ ) # Maintain the order of strains
107
+
108
+ p <- ggplot(long_data, aes(x = strain, y = Count, fill = Category)) +
109
+ geom_bar(position = "fill", stat = "identity") +
110
+ coord_flip() +
111
+ theme_minimal() +
112
+ labs(x = "Strains", y = "Proportion", fill = "Category") +
113
+ scale_fill_manual(values = c("single_cloud" = "#72B063", "others" = "#719AAC", "half_core" = "#E29135"), labels = c("Single", "Others", "Half")) +
114
+ scale_x_discrete(
115
+ breaks = function(x) {
116
+ y_levels <- levels(factor(long_data$strain))
117
+ # 如果类别数大于 20,选择均匀的 20 个类别
118
+ if (length(y_levels) > 25) {
119
+ return(y_levels[seq(1, length(y_levels), length.out = 25)]) # 选择 20 个类别
120
+ } else {
121
+ return(y_levels) # 如果类别少于 20 个,显示所有
122
+ }
123
+ }
124
+ )
125
+ return(p)
126
+ }
127
+
128
+ draw_gene_code <- function(input_code) {
129
+ df <- as.data.frame(as.table(as.matrix(input_code)))
130
+ df <- df %>%
131
+ rename(Count = Freq)
132
+ df$Count <- as.numeric(as.character(df$Count))
133
+ p <- ggplot(df, aes(x = Var1, y = Var2, fill = Count)) +
134
+ geom_tile() +
135
+ scale_fill_gradient2(high = "#990033", low = "white") +
136
+ theme_bw() +
137
+ theme(panel.grid = element_blank(), axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5), ) +
138
+ xlab("Start|Stop codon") +
139
+ ylab(NULL) +
140
+ scale_y_discrete(
141
+ breaks = function(x) {
142
+ # 获取 Var2 的所有类别
143
+ y_levels <- levels(factor(df$Var2))
144
+ if (length(y_levels) > 25) {
145
+ return(y_levels[seq(1, length(y_levels), length.out = 25)])
146
+ } else {
147
+ return(y_levels) # 如果类别少于 20 个,显示所有
148
+ }
149
+ }
150
+ )
151
+ return(p)
152
+ }
153
+
154
+ A <- draw_ani(input_prep, as.numeric(opt$ani_thre))
155
+ B <- draw_gene_number(input_prep)
156
+ C <- draw_half_core(input_prep)
157
+ D <- draw_proportion(input_prep)
158
+ E <- draw_gene_code(input_code)
159
+ save_basic_plots(A, B, C, D, E, opt$single_file, opt$output_dir)
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env python3
2
+ # Script by Jason Kwong & Torsten Seemann
3
+ # Script to mask recombination from CFML output and draw SVG of recombinant regions
4
+
5
+ # Usage
6
+ import argparse
7
+ from argparse import RawTextHelpFormatter
8
+
9
+ parser = argparse.ArgumentParser(
10
+ formatter_class=RawTextHelpFormatter,
11
+ description='Mask recombination from ClonalFrameML/Gubbins output and draw SVG of recombinant regions',
12
+ usage='\n %(prog)s --aln FASTA --out OUTFILE [--gubbins] <PREFIX>')
13
+ parser.add_argument('prefix', metavar='PREFIX', help='prefix used for CFML/Gubbins input files (required)')
14
+ parser.add_argument('--gubbins', action='store_true', help='parse as Gubbins instead of ClonalFrameML')
15
+ parser.add_argument('--aln', metavar='FASTA', required=True, help='multiFASTA alignment used as input for CFML (required)')
16
+ parser.add_argument('--out', metavar='OUTFILE', default='maskrc.aln', help='output file for masked alignment (default="maskrc.aln")')
17
+ parser.add_argument('--symbol', metavar='CHAR', default='?', help='symbol to use for masking (default="?")')
18
+ parser.add_argument('--regions', metavar='FILE', help='output recombinant regions to file')
19
+ parser.add_argument('--svg', metavar='FILE', help='draw SVG output of recombinant regions and save as specified file')
20
+ parser.add_argument('--svgsize', metavar='WIDExHIGH', default='800x600', help='specify width and height of SVG in pixels (default="800x600")')
21
+ parser.add_argument('--svgorder', metavar='FILE', help='specify file containing list of taxa (1 per line) in desired order')
22
+ parser.add_argument('--svgcolour', metavar='COLOUR', default='black', help='specify colour of recombination regions in HEX format (default=black)')
23
+ parser.add_argument('--consensus', action='store_true', help='add consensus row of recombination hotspots')
24
+ parser.add_argument('--version', action='version', version='%(prog)s 0.5')
25
+
26
+ args = parser.parse_args()
27
+
28
+ prefix = str(args.prefix)
29
+ treefn = prefix + ('.labelled_tree.newick', '.final_tree.tre')[args.gubbins]
30
+ coordfn = prefix + ('.importation_status.txt', '.recombination_predictions.gff')[args.gubbins]
31
+
32
+ import os
33
+ import sys
34
+ import csv
35
+ from collections import defaultdict
36
+ from ete3 import Tree
37
+ from Bio import SeqIO
38
+ from Bio.Seq import Seq
39
+ from Bio.Seq import MutableSeq
40
+ from Bio.SeqRecord import SeqRecord
41
+ import svgwrite
42
+ from BCBio import GFF
43
+ import pprint
44
+
45
+ # Functions
46
+ def msg(*args, **kwargs):
47
+ print(*args, file=sys.stderr, **kwargs)
48
+
49
+ def err(*args, **kwargs):
50
+ msg(*args, **kwargs)
51
+ sys.exit(1);
52
+
53
+ def check_file(f):
54
+ msg('Checking file: {}'.format(f))
55
+ if os.path.isfile(f) == False:
56
+ err('ERROR: Cannot find "{}". Check output files exist in this directory.'.format(f))
57
+
58
+ # Check input files (output from CFML)
59
+ check_file(treefn)
60
+ check_file(coordfn)
61
+
62
+ # Setup lists and dictionary
63
+ d = defaultdict(list)
64
+ seqLIST = []
65
+ leafLIST = []
66
+ t = Tree(treefn, format=1)
67
+
68
+ # Set taxa order for SVG
69
+ if args.svgorder:
70
+ msg('Setting taxa order using --svgorder {}'.format(args.svgorder));
71
+ with open(args.svgorder) as file:
72
+ for row in file:
73
+ leafLIST.append(row.rstrip())
74
+ else:
75
+ msg('Setting taxa order using leaves from: {}'.format(treefn));
76
+ for leaf in t:
77
+ leafLIST.append(leaf.name)
78
+
79
+ # gubbins GFF
80
+ # node="internal_13->2016-04538";neg_log_likelihood="3764.359443";taxa="2016-04538";snp_count="108";
81
+ # node="internal_ROOT->internal_10";neg_log_likelihood="47939.649932";taxa=" 2016-23066 2015-19014";snp_count="8";
82
+ # node="internal_15->internal_16";neg_log_likelihood="574.375212";taxa=" 2008-16004 2010-22387";snp_count="39";
83
+ # node="internal_15->internal_16";neg_log_likelihood="418.840895";taxa=" 2008-16004 2010-22387";snp_count="38";
84
+ # node="internal_12->internal_14";neg_log_likelihood="17258.115166";taxa=" 2012-21203 2008-20031 2008-16004 2010-22387";
85
+
86
+ # clonalframeml "BED"
87
+ #Node Beg End
88
+ #2016-11776 5624 6619
89
+ #2016-11776 10778 11326
90
+ #Reference 2489729 2490893
91
+ #NODE_10 1165936 1166465
92
+
93
+ # internal
94
+ # ['Reference', ['2434777', '2434933', 'ancestral']],
95
+ # ['2016-17702', ['2483546', '2485711', 'ancestral']],
96
+ # ['2016-17705', ['2483546', '2485711', 'ancestral']],
97
+ # ['2016-11776', ['2483546', '2485711', 'ancestral']],
98
+ # ['2016-11778', ['2483546', '2485711', 'ancestral']],
99
+ # ['2016-11777', ['2483546', '2485711', 'ancestral']],
100
+ # ['2016-17701', ['2483546', '2485711', 'ancestral']],
101
+ # ['Reference', ['2483546', '2485711', 'ancestral']]]
102
+
103
+ if args.gubbins:
104
+ msg('Parsing Gubbins file: {}'.format(coordfn))
105
+ with open(coordfn) as gff:
106
+ for rec in GFF.parse(gff):
107
+ for f in rec.features:
108
+ taxa = f.qualifiers['taxa'][0].strip().split()
109
+ state = ('ancestral', 'extant')[ len(taxa)==1 ]
110
+ for taxon in taxa:
111
+ # BCBio-GFF module parser seems to extract coordinate-1 for f.location.start
112
+ seqLIST.append( [ taxon, [ int(f.location.start)+1, int(f.location.end), state ] ] )
113
+ else:
114
+ msg('Parsing ClonalFrameML file: {}'.format(coordfn))
115
+ with open(coordfn) as csvfile:
116
+ RCseqs = csv.reader(csvfile, delimiter='\t')
117
+ next(csvfile)
118
+ for row in RCseqs:
119
+ seq = row[0]
120
+ RCstart = row[1]
121
+ RCstop = row[2]
122
+ node = t.search_nodes(name=seq)[0]
123
+ if node.is_leaf():
124
+ leaf_names = [node.name]
125
+ z = 'extant'
126
+ else:
127
+ leaves = node.get_leaves()
128
+ leaf_names = [leaf.name for leaf in leaves]
129
+ z = 'ancestral'
130
+ for l in leaf_names:
131
+ seqLIST.append([l, [RCstart, RCstop, z]])
132
+
133
+ # Build dictionary of recombinant regions
134
+ for k,v in seqLIST:
135
+ d[k].append(v)
136
+
137
+ # Mask recombination in sequences
138
+ seqALN = []
139
+ for record in SeqIO.parse(args.aln, 'fasta'):
140
+ msg('Reading {} ... '.format(record.id))
141
+ seqlen = len(record.seq)
142
+ regions = d.get(record.id, None)
143
+ newrec = MutableSeq(str(record.seq))
144
+ if regions:
145
+ for a in regions:
146
+ start = int(a[0]) - 1
147
+ end = int(a[1])
148
+ lenMASK = end - start
149
+ newrec[start:end] = (args.symbol)*lenMASK
150
+ seqALN.append(SeqRecord(Seq(str(newrec)), record.id, description=''))
151
+
152
+ # Write masked alignment to file
153
+ msg('Writing masked alignment to {} ... '.format(args.out))
154
+ SeqIO.write(seqALN, args.out, 'fasta')
155
+
156
+ # Write recombinant regions to file
157
+ if args.regions:
158
+ with open(args.regions, 'w') as csvfile:
159
+ csvwriter = csv.writer(csvfile, delimiter='\t')
160
+ for k,v in list(d.items()):
161
+ for b in v:
162
+ x = b[0]
163
+ y = b[1]
164
+ z = b[2]
165
+ csvwriter.writerow([k,x,y,z])
166
+
167
+ # Draw SVG
168
+ svgsize = args.svgsize.split('x',1)
169
+ width = int(svgsize[0])
170
+ height = int(svgsize[1])
171
+ numseqs = len(leafLIST)
172
+ h = height/float(numseqs)
173
+ s = width/float(int(seqlen))
174
+ fsize = int(h*0.9)
175
+ font = 'font-size:{}px; font-family:Arial'.format(fsize)
176
+ colour = args.svgcolour
177
+ main_colour = 'black'
178
+ ancestral_colour = '#A9A9A9'
179
+ interval = 500000 # Tick interval in bp
180
+
181
+ dwg = svgwrite.Drawing(args.svg)
182
+
183
+ def rect(x,p,w,colour): # Draw regions
184
+ dwg.add(dwg.rect(insert=((x*s)+5, (p*h)+5), size=(w, h*0.8), fill=colour))
185
+ if args.consensus:
186
+ dwg.add(dwg.rect(insert=((x*s)+5, 5), size=(w, h*0.8), fill=main_colour))
187
+
188
+ def label(n,p): # Sequence ID labels
189
+ dwg.add(dwg.text(n, insert=((seqlen*s)+15, (((p+1)*h)-(0.2*h))+5), fill=main_colour, style=font))
190
+
191
+ def ticks(q): # Draw tick marks
192
+ count = interval
193
+ while count < seqlen:
194
+ dwg.add(dwg.line((count*s,((q)*h)), (count*s,((q)*h)-3), stroke=main_colour))
195
+ dwg.add(dwg.text((count/1000000.0), insert=((count*s)-6, ((q)*h)+13), fill=main_colour, style=font))
196
+ count = count + interval
197
+
198
+ def box(width,h): # Box with 5px margin
199
+ dwg.add(dwg.line((0,0), ((width+10),0), stroke=main_colour))
200
+ dwg.add(dwg.line(((width+10),0), ((width+10),((p+1)*h)), stroke=main_colour))
201
+ dwg.add(dwg.line(((width+10),((p+1)*h)), (0,((p+1)*h)), stroke=main_colour))
202
+ dwg.add(dwg.line((0,((p+1)*h)), (0,0), stroke=main_colour))
203
+
204
+ # Draw recombinant regions and labels
205
+ if args.svg:
206
+ msg('Drawing SVG to {} ...'.format(args.svg))
207
+ if args.consensus:
208
+ p = 1
209
+ dwg.add(dwg.text('Consensus', insert=((seqlen*s)+15, (0.8*h)+5), fill=main_colour, style=font))
210
+ else:
211
+ p = 0
212
+ for seq in leafLIST:
213
+ v = d.get(seq, None)
214
+ if v:
215
+ for b in v:
216
+ x = int(b[0])
217
+ y = int(b[1])
218
+ z = b[2]
219
+ w = (y-x)*s
220
+ if w < 1:
221
+ w = 1
222
+ if z == 'extant':
223
+ rect(x,p,w,colour) # regions
224
+ elif z == 'ancestral':
225
+ rect(x,p,w,ancestral_colour)
226
+ label(seq,p) # labels
227
+ p = p+1
228
+ # Draw bounding box
229
+ box(width,h)
230
+ # Add tick marks
231
+ ticks(p+1)
232
+ dwg.save()
233
+
234
+ # Exit
235
+ msg('Done.')
236
+ sys.exit(0)