0
|
1 #!/usr/bin/env Rscript
|
|
2
|
|
3 suppressPackageStartupMessages(library("adegenet"))
|
|
4 suppressPackageStartupMessages(library("ape"))
|
7
|
5 suppressPackageStartupMessages(library("data.table"))
|
12
|
6 suppressPackageStartupMessages(library("dbplyr"))
|
7
|
7 suppressPackageStartupMessages(library("dplyr"))
|
0
|
8 suppressPackageStartupMessages(library("ggplot2"))
|
|
9 suppressPackageStartupMessages(library("knitr"))
|
4
|
10 suppressPackageStartupMessages(library("optparse"))
|
|
11 suppressPackageStartupMessages(library("poppr"))
|
|
12 suppressPackageStartupMessages(library("RColorBrewer"))
|
7
|
13 suppressPackageStartupMessages(library("RPostgres"))
|
12
|
14 suppressPackageStartupMessages(library("tidyr"))
|
4
|
15 suppressPackageStartupMessages(library("vcfR"))
|
|
16 suppressPackageStartupMessages(library("vegan"))
|
0
|
17
|
|
18 option_list <- list(
|
7
|
19 make_option(c("--database_connection_string"), action="store", dest="database_connection_string", help="Corals (stag) database connection string"),
|
|
20 make_option(c("--input_affy_metadata"), action="store", dest="input_affy_metadata", help="Affymetrix 96 well plate input file"),
|
4
|
21 make_option(c("--input_pop_info"), action="store", dest="input_pop_info", help="Population information input file"),
|
7
|
22 make_option(c("--input_vcf"), action="store", dest="input_vcf", help="VCF input file"),
|
|
23 make_option(c("--output_stag_db_report"), action="store", dest="output_stag_db_report", help="stag db report output file")
|
0
|
24 )
|
|
25
|
|
26 parser <- OptionParser(usage="%prog [options] file", option_list=option_list);
|
|
27 args <- parse_args(parser, positional_arguments=TRUE);
|
|
28 opt <- args$options;
|
|
29
|
|
30 get_file_path = function(file_name) {
|
|
31 file_path = paste("output_plots_dir", file_name, sep="/");
|
|
32 return(file_path);
|
|
33 }
|
|
34
|
8
|
35 get_database_connection <- function(db_conn_string) {
|
|
36 # Instantiate database connection.
|
|
37 # The connection string has this format:
|
|
38 # postgresql://user:password@host/dbname
|
|
39 conn_items <- strsplit(db_conn_string, "://")[[1]];
|
|
40 string_needed <- conn_items[2];
|
|
41 items_needed <- strsplit(string_needed, "@")[[1]];
|
|
42 user_pass_string <- items_needed[1];
|
|
43 host_dbname_string <- items_needed[2];
|
|
44 user_pass_items <- strsplit(user_pass_string, ":")[[1]];
|
|
45 host_dbname_items <- strsplit(host_dbname_string, "/")[[1]];
|
|
46 user <- user_pass_items[1];
|
|
47 pass <- user_pass_items[2];
|
|
48 host <- host_dbname_items[1];
|
|
49 dbname <- host_dbname_items[2];
|
|
50 # FIXME: is there a way to not hard-code the port?
|
16
|
51 conn <- DBI::dbConnect(RPostgres::Postgres(), host=host, port="5432", dbname=dbname, user=user, password=pass);
|
8
|
52 return (conn);
|
|
53 }
|
|
54
|
3
|
55 # Read in VCF input file.
|
2
|
56 vcf <- read.vcfR(opt$input_vcf);
|
0
|
57
|
7
|
58 # Convert VCF file into a genind for the Poppr package.
|
|
59 # TODO: probably should not hard-code 2 cores.
|
|
60 gl <- vcfR2genlight(vcf, n.cores=2);
|
8
|
61 gind <- new("genind", (as.matrix(gl)));
|
7
|
62
|
0
|
63 # Add population information to the genind object.
|
10
|
64 poptab <- read.table(opt$input_pop_info, check.names=FALSE, header=F, na.strings=c("", "NA"), stringsAsFactors=FALSE, sep="\t");
|
9
|
65 colnames(poptab) <- c("row_id", "affy_id", "user_specimen_id", "region");
|
8
|
66 gind@pop <- as.factor(poptab$region);
|
7
|
67
|
|
68 # Convert genind object to a genclone object.
|
8
|
69 obj2 <- as.genclone(gind);
|
7
|
70
|
|
71 # Calculate the bitwise distance between individuals.
|
8
|
72 xdis <- bitwise.dist(obj2);
|
0
|
73
|
9
|
74 # Multilocus genotypes (threshold of 16%).
|
|
75 mlg.filter(obj2, distance=xdis) <- 0.016;
|
8
|
76 m <- mlg.table(obj2, background=TRUE, color=TRUE);
|
0
|
77
|
|
78 # Create table of MLGs.
|
8
|
79 id <- mlg.id(obj2);
|
7
|
80 dt <- data.table(id, keep.rownames=TRUE);
|
9
|
81 setnames(dt, c("id"), c("affy_id"));
|
7
|
82
|
16
|
83 # Read user's Affymetrix 96 well plate tabular file.
|
12
|
84 pinfo <- read.table(opt$input_affy_metadata, header=FALSE, stringsAsFactors=FALSE, sep="\t");
|
16
|
85 colnames(pinfo) <- c("user_specimen_id", "field_call", "bcoral_genet_id", "bsym_genet_id", "reef",
|
|
86 "region", "latitude", "longitude", "geographic_origin", "sample_location",
|
|
87 "latitude_outplant", "longitude_outplant", "depth", "dist_shore", "disease_resist",
|
|
88 "bleach_resist", "mortality","tle", "spawning", "collector_last_name",
|
|
89 "collector_first_name", "org", "collection_date", "contact_email", "seq_facility",
|
|
90 "array_version", "public", "public_after_date", "sperm_motility", "healing_time",
|
|
91 "dna_extraction_method", "dna_concentration");
|
9
|
92 pinfo$user_specimen_id <- as.character(pinfo$user_specimen_id);
|
|
93 pinfo2 <- as.character(pinfo$user_specimen_id);
|
|
94 pi <- data.table(pinfo2);
|
|
95 setnames(pi, c("pinfo2"), c("user_specimen_id"));
|
7
|
96
|
8
|
97 # Connect to database.
|
|
98 conn <- get_database_connection(opt$database_connection_string);
|
7
|
99
|
|
100 # Import the sample table.
|
16
|
101 sample_table <- tbl(conn, "sample");
|
|
102
|
|
103 # Import the genotype table.
|
|
104 genotype_table <- tbl(conn, "genotype");
|
7
|
105
|
16
|
106 # Select columns from the sample table and the
|
|
107 # genotype table joined by genotype_id.
|
|
108 sample_table_columns <- sample_table %>% select(user_specimen_id, affy_id, genotype_id);
|
|
109 smlg <- sample_table_columns %>%
|
|
110 left_join(genotype_table %>%
|
|
111 select("id", "coral_mlg_clonal_id", "symbio_mlg_clonal_id"),
|
|
112 by=c("genotype_id" = "id"));
|
7
|
113
|
|
114 # Convert to dataframe.
|
|
115 sm <- data.frame(smlg);
|
16
|
116 # Name the columns.
|
|
117 colnames(sm) <- c("user_specimen_id", "affy_id", "genotype_id", "coral_mlg_clonal_id", "symbio_mlg_clonal_id");
|
7
|
118
|
12
|
119 # Missing GT in samples submitted.
|
|
120 gt <- extract.gt(vcf, element="GT", as.numeric=FALSE);
|
|
121 myMiss <- apply(gt, MARGIN=2, function(x){ sum(is.na(x))});
|
|
122 myMiss <- (myMiss / nrow(vcf)) * 100;
|
|
123 miss <- data.frame(myMiss);
|
|
124
|
7
|
125 # Convert missing data into data table.
|
8
|
126 mi <-setDT(miss, keep.rownames=TRUE)[];
|
9
|
127 setnames(mi, c("rn"), c("affy_id"));
|
7
|
128 setnames(mi, c("myMiss"), c("percent_missing_data_coral"));
|
|
129 # Round missing data to two digits.
|
9
|
130 mi$percent_missing_data_coral <- round(mi$percent_missing_data_coral, digits=2);
|
7
|
131
|
12
|
132 hets <- apply(gt, MARGIN=2, function(x) {sum(lengths(regmatches(x, gregexpr("0/1", x))))} );
|
|
133 hets <- (hets / nrow(vcf)) * 100;
|
|
134 ht <- data.frame(hets);
|
|
135
|
7
|
136 # Convert heterozygosity data into data table.
|
|
137 ht <-setDT(ht, keep.rownames=TRUE)[];
|
9
|
138 setnames(ht, c("rn"), c("affy_id"));
|
7
|
139 setnames(ht, c("hets"), c("percent_mixed_coral"));
|
|
140 # Round missing data to two digits.
|
|
141 ht$percent_mixed<-round(ht$percent_mixed, digits=2);
|
|
142
|
12
|
143 refA <- apply(gt, MARGIN=2, function(x) {sum(lengths(regmatches(x, gregexpr("0/0", x))))} );
|
|
144 refA <- (refA / nrow(vcf)) * 100;
|
|
145 rA <- data.frame(refA);
|
|
146
|
7
|
147 # Convert refA data into data.table.
|
|
148 rA <-setDT(rA, keep.rownames=TRUE)[];
|
9
|
149 setnames(rA, c("rn"), c("affy_id"));
|
7
|
150 setnames(rA, c("refA"), c("percent_reference_coral"));
|
|
151 # round missing data to two digits.
|
|
152 rA$percent_reference<-round(rA$percent_reference, digits=2);
|
|
153
|
12
|
154 altB <- apply(gt, MARGIN=2, function(x) {sum(lengths(regmatches(x, gregexpr("1/1", x))))} );
|
|
155 altB <- (altB / nrow(vcf)) * 100;
|
|
156 aB <- data.frame(altB);
|
|
157
|
7
|
158 # Convert altB data into data table.
|
|
159 aB <-setDT(aB, keep.rownames=TRUE)[];
|
9
|
160 setnames(aB, c("rn"), c("affy_id"));
|
7
|
161 setnames(aB, c("altB"), c("percent_alternative_coral"));
|
|
162 # Round missing data to two digits.
|
|
163 aB$percent_alternative<-round(aB$percent_alternative, digits=2);
|
|
164
|
|
165 #convert mlg id to data.table format
|
|
166 dt <- data.table(id, keep.rownames=TRUE);
|
9
|
167 setnames(dt, c("id"), c("affy_id"));
|
7
|
168
|
|
169 # Transform.
|
|
170 df3 <- dt %>%
|
|
171 group_by(row_number()) %>%
|
16
|
172 dplyr::rename(group="row_number()") %>%
|
12
|
173 unnest (affy_id) %>%
|
7
|
174 # Join with mlg table.
|
|
175 left_join(sm %>%
|
9
|
176 select("affy_id","coral_mlg_clonal_id"),
|
16
|
177 by="affy_id");
|
7
|
178
|
|
179 # If found in database, group members on previous mlg id.
|
|
180 uniques <- unique(df3[c("group", "coral_mlg_clonal_id")]);
|
|
181 uniques <- uniques[!is.na(uniques$coral_mlg_clonal_id),];
|
|
182 na.mlg <- which(is.na(df3$coral_mlg_clonal_id));
|
|
183 na.group <- df3$group[na.mlg];
|
|
184 df3$coral_mlg_clonal_id[na.mlg] <- uniques$coral_mlg_clonal_id[match(na.group, uniques$group)];
|
|
185
|
|
186 # Determine if the sample mlg matched previous genotyped sample.
|
|
187 df4<- df3 %>%
|
|
188 group_by(group) %>%
|
12
|
189 mutate(DB_match = ifelse(is.na(coral_mlg_clonal_id),"no_match", "match"));
|
7
|
190
|
|
191 # Create new mlg id for samples that did not match those in the database.
|
|
192 none <- unique(df4[c("group", "coral_mlg_clonal_id")]);
|
|
193 none <- none[is.na(none$coral_mlg_clonal_id),];
|
|
194 na.mlg2 <- which(is.na(df4$coral_mlg_clonal_id));
|
|
195 n.g <- df4$group[na.mlg2];
|
|
196 ct <- length(unique(n.g));
|
|
197
|
|
198 # List of new group ids, the sequence starts at the number of
|
|
199 # ids present in df4$coral_mlg_clonal_ids plus 1. Not sure if
|
|
200 # the df4 file contains all ids. If it doesn't then look below
|
|
201 # to change the seq() function.
|
|
202 n.g_ids <- sprintf("HG%04d", seq((sum(!is.na(unique(df4["coral_mlg_clonal_id"]))) + 1), by=1, length=ct));
|
16
|
203 # Pair group with new ids.
|
7
|
204 rat <- cbind(unique(n.g), n.g_ids);
|
16
|
205 # Assign the new id iteratively for all that have NA.
|
7
|
206 for (i in 1:length(na.mlg2)) {
|
|
207 df4$coral_mlg_clonal_id[na.mlg2[i]] <- n.g_ids[match(df4$group[na.mlg2[i]], unique(n.g))];
|
|
208 }
|
|
209
|
16
|
210 # Subset poptab for all samples.
|
9
|
211 subpop <- poptab[c(2, 3)];
|
|
212
|
7
|
213 # Merge data frames for final table.
|
|
214 report_user <- pi %>%
|
9
|
215 left_join(subpop %>%
|
|
216 select("affy_id", "user_specimen_id"),
|
16
|
217 by="user_specimen_id") %>%
|
9
|
218 left_join(df4 %>%
|
|
219 select("affy_id", "coral_mlg_clonal_id", "DB_match"),
|
16
|
220 by="affy_id") %>%
|
7
|
221 left_join(mi %>%
|
9
|
222 select("affy_id", "percent_missing_data_coral"),
|
16
|
223 by="affy_id") %>%
|
7
|
224 left_join(ht %>%
|
9
|
225 select("affy_id", "percent_mixed_coral"),
|
16
|
226 by="affy_id") %>%
|
7
|
227 left_join(rA %>%
|
9
|
228 select("affy_id", "percent_reference_coral"),
|
16
|
229 by="affy_id") %>%
|
7
|
230 left_join(aB %>%
|
9
|
231 select("affy_id", "percent_alternative_coral"),
|
16
|
232 by="affy_id") %>%
|
7
|
233 mutate(DB_match = ifelse(is.na(DB_match), "failed", DB_match))%>%
|
12
|
234 mutate(coral_mlg_clonal_id = ifelse(is.na(coral_mlg_clonal_id), "failed", coral_mlg_clonal_id)) %>%
|
7
|
235 ungroup() %>%
|
|
236 select(-group);
|
|
237
|
12
|
238 write.csv(report_user, file=opt$output_stag_db_report, quote=FALSE);
|
0
|
239
|
9
|
240 # Combine sample information for database.
|
|
241 report_db <- pinfo %>%
|
|
242 left_join(report_user %>%
|
|
243 select("user_specimen_id", "affy_id", "coral_mlg_clonal_id", "DB_match",
|
|
244 "percent_missing_data_coral", "percent_mixed_coral", "percent_reference_coral",
|
|
245 "percent_alternative_coral"),
|
16
|
246 by="user_specimen_id");
|
7
|
247
|
9
|
248 # Create vector indicating number of individuals desired
|
|
249 # made from affy_id collumn of report_user data table.
|
|
250 i <- report_user[[2]];
|
|
251 sub96 <- obj2[i, mlg.reset=FALSE, drop=FALSE];
|
0
|
252
|
4
|
253 # Create a phylogeny of samples based on distance matrices.
|
16
|
254 cols <- palette(brewer.pal(n=12, name="Set3"));
|
4
|
255 set.seed(999);
|
|
256 # Start PDF device driver.
|
|
257 dev.new(width=10, height=7);
|
|
258 file_path = get_file_path("nj_phylogeny.pdf");
|
|
259 pdf(file=file_path, width=10, height=7);
|
|
260 # Organize branches by clade.
|
9
|
261 theTree <- sub96 %>%
|
|
262 aboot(dist=provesti.dist, sample=1, tree="nj", cutoff=50, quiet=TRUE) %>%
|
7
|
263 ladderize();
|
9
|
264 theTree$tip.label <- report_user$user_specimen_id[match(theTree$tip.label, report_user$affy_id)];
|
|
265 plot.phylo(theTree, tip.color=cols[sub96$pop], label.offset=0.0125, cex=0.3, font=2, lwd=4, align.tip.label=F, no.margin=T);
|
4
|
266 # Add a scale bar showing 5% difference..
|
9
|
267 add.scale.bar(0, 0.95, length=0.05, cex=0.65, lwd=3);
|
|
268 nodelabels(theTree$node.label, cex=.5, adj=c(1.5, -0.1), frame="n", font=3, xpd=TRUE);
|
|
269 legend("topright", legend=c("Antigua", "Bahamas", "Belize", "Cuba", "Curacao", "Florida", "PuertoRico", "USVI"), text.col=cols, xpd=T, cex=0.8);
|
7
|
270 dev.off();
|
0
|
271
|
9
|
272 # Missing data barplot.
|
|
273 poptab$miss <- report_user$percent_missing_data_coral[match(miss$affy_id, report_user$affy_id)];
|
|
274 test2 <- which(!is.na(poptab$miss));
|
|
275 miss96 <- poptab$miss[test2];
|
|
276 name96 <- poptab$user_specimen_id[test2];
|
|
277 dev.new(width=10, height=7);
|
|
278 file_path = get_file_path("missing_data.pdf");
|
|
279 pdf (file=file_path, width=10, height=7);
|
|
280 par(mar = c(8, 4, 4, 2));
|
|
281 x <- barplot(miss96, las=2, col=cols, ylim=c(0, 3), cex.axis=0.8, space=0.8, ylab="Missingness (%)", xaxt="n");
|
|
282 text(cex=0.6, x=x-0.25, y=-.05, name96, xpd=TRUE, srt=60, adj=1);
|
|
283 dev.off()
|
|
284
|
15
|
285 # Generate a pie chart for each sample with a genotype.
|
|
286 # Store the numerical and user_specimen_id values from
|
|
287 # report_user for the charts (user_specimen_id names
|
|
288 # will be used to label each chart).
|
12
|
289 dt1 <- data.table(report_user);
|
|
290 dt1 <- report_user[c(-2, -3, -4)];
|
|
291 dt1 <- na.omit(dt1);
|
15
|
292 # Translate to N (i.e., number of samples with a
|
|
293 # genotype) columns and 5 rows.
|
12
|
294 tdt1 <- t(dt1);
|
|
295 # Make another data table and transpose it the same as dt1 to
|
15
|
296 # get numerics. These will feed into the creation of N vectors.
|
12
|
297 dt2 <- data.table(report_user);
|
|
298 dt2 <- report_user[c(-1, -2, -3, -4)];
|
15
|
299 # Translate to N columns and 5 rows.
|
12
|
300 tdt2 <- t(dt2);
|
|
301 tdt1_matrix <- as.matrix(tdt1[-1,]);
|
15
|
302 # The number of columns is the number of samples with genotypes.
|
|
303 nc <- ncol(tdt1_matrix);
|
12
|
304 mode(tdt1_matrix) <- "numeric";
|
|
305 spy <- rowMeans(tdt1_matrix);
|
|
306 dev.new(width=10, height=7);
|
|
307 file_path = get_file_path("percent_breakdown.pdf");
|
|
308 pdf(file=file_path, width=10, height=7);
|
|
309 # Average pie of all samples.
|
|
310 labels <- paste(c("missing data", "mixed", "reference", "alternative"), " (", round(spy, 1), "%)", sep="");
|
|
311 col <- c("GREY", "#006DDB", "#24FF24", "#920000");
|
|
312 main <- "Average breakdown of SNP assignments across all samples";
|
|
313 pie(spy, labels=labels, radius=0.60, col=col, main=main, cex.main=.75);
|
|
314 par(mfrow=c(3, 2));
|
14
|
315 col <- c("GREY", "#006DDB", "#24FF24", "#920000");
|
15
|
316 for (i in 1:nc) {
|
14
|
317 tmp_labels <- paste(labels, " (", round(tdt1_matrix[,i], 1), "%)", sep="");
|
12
|
318 main <- paste("Breakdown of SNP assignments for", tdt1[1, i]);
|
14
|
319 pie(tdt1_matrix[,i], labels=tmp_labels, radius=0.90, col=col, main=main, cex.main=.85, cex=0.75);
|
12
|
320 }
|
|
321 dev.off()
|
9
|
322
|