Mercurial > repos > kaymccoy > aggregate_fitnesses
comparison aggregate.py @ 4:9dd574c52765 draft
Uploaded
author | kaymccoy |
---|---|
date | Thu, 11 Aug 2016 18:30:23 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
3:07ef2716dc03 | 4:9dd574c52765 |
---|---|
1 # A translation of aggregate.pl into python! For analysis of Tn-Seq. | |
2 # This script requires BioPython just like calc_fitness.py, so you need it installed along with its dependencies if you want to run these scripts on your own. | |
3 # How to install BioPython and a list of its dependencies can be found here: http://biopython.org/DIST/docs/install/Installation.html | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 ##### ARGUMENTS ##### | |
15 | |
16 # Prints basic instructions on / options for using this code; called when the user forgets to enter an output file or fitness file(s) | |
17 | |
18 def print_usage(): | |
19 print "Aggregate.py's usage is as follows:" + "\n\n" | |
20 print "\033[1m" + "Required" + "\033[0m" + "\n" | |
21 print "-o" + "\t\t" + "Output file for aggregated data." + "\n" | |
22 print "\n" | |
23 print "\033[1m" + "Optional" + "\033[0m" + "\n" | |
24 print "-c" + "\t\t" + "Check for missing genes in the data set - provide a reference genome in genbank format. Missing genes will be sent to stdout." + "\n" | |
25 print "-m" + "\t\t" + "Place a mark in an extra column for this set of genes. Provide a file with a list of genes seperated by newlines." + "\n" | |
26 print "-x" + "\t\t" + "Cutoff: Don't include fitness scores with average counts (c1+c2)/2 < x (default: 0)" + "\n" | |
27 print "-b" + "\t\t" + "Blanks: Exclude -b % of blank fitness scores (scores where c2 = 0) (default: 0 = 0%)" + "\n" | |
28 print "-w" + "\t\t" + "Use weighted algorithm to calculate averages, variance, sd, se" + "\n" | |
29 print "-l" + "\t\t" + "Weight ceiling: maximum value to use as a weight (default: 999,999)" + "\n" | |
30 print "\n" | |
31 print "All remainder arguements will be treated as fitness files (those files created by calc_fitness.py)" + "\n" | |
32 print "\n" | |
33 | |
34 # Turns the arguments entered in the command line into variables so that they can actually be called. | |
35 | |
36 import argparse | |
37 parser = argparse.ArgumentParser() | |
38 parser.add_argument("-o", action="store", dest="summary") | |
39 parser.add_argument("-c", action="store", dest="find_missing") | |
40 parser.add_argument("-m", action="store", dest="marked") | |
41 parser.add_argument("-x", action="store", dest="cutoff") | |
42 parser.add_argument("-b", action="store", dest="blank_pc") | |
43 parser.add_argument("-w", action="store", dest="weighted") | |
44 parser.add_argument("-l", action="store", dest="weight_ceiling") | |
45 parser.add_argument("fitnessfiles", nargs=argparse.REMAINDER) | |
46 | |
47 #Shortens the names of those variables created from the arguments - from parser.parse_args().ref_genome to arguments.ref_genome, for example. | |
48 | |
49 arguments = parser.parse_args() | |
50 | |
51 #Checks that the required arguments have been entered; if not informs the user they need to provide a name for the output file / the fitness file(s) and print's aggregate.py's usage | |
52 | |
53 if not arguments.summary: | |
54 print "\n" + "You are missing a value for the -o flag. " | |
55 print_usage() | |
56 quit() | |
57 | |
58 if not arguments.fitnessfiles: | |
59 print "\n" + "You are missing fitness file(s); these should be entered immediately after all the flags. " | |
60 print_usage() | |
61 quit() | |
62 | |
63 #Sets the maximum weight of a fitness value, if that wasn't specified in the command line. | |
64 | |
65 if (not arguments.weight_ceiling): | |
66 arguments.max_weight = 999999 | |
67 | |
68 #Sets the cutoff to a default value of 0 if it wasn't specified in the command line. | |
69 #Cutoff exists to discard positions with a low number of counted transcripts, because their fitness may not be as accurate - for the same reasoning that studies with low sample sizes can be innacurate. | |
70 | |
71 if (not arguments.cutoff): | |
72 arguments.cutoff = 0 | |
73 | |
74 #Sets the % of blank fitness values to exclude to a default value of 0 if it wasn't specified in the command line. This can be found in the 2nd output of calc_fitness. | |
75 | |
76 if (not arguments.blank_pc): | |
77 arguments.blank_pc = 0 | |
78 | |
79 # gets blank_pc from the output file of calc_fit / consol | |
80 | |
81 if arguments.blank_pc: | |
82 with open(arguments.blank_pc) as file: | |
83 blank_pc = file.read().splitlines() | |
84 arguments.blank_pc = float(blank_pc[1].split()[1]) | |
85 | |
86 | |
87 | |
88 | |
89 | |
90 | |
91 | |
92 ##### SUBROUTINES ##### | |
93 | |
94 #A subroutine that calculates the average, variance, standard deviation (sd), and standard error (se) of a group of scores; for use when aggregating scores by gene later on | |
95 | |
96 import math | |
97 def average(scores): | |
98 sum = 0 | |
99 num = 0 | |
100 | |
101 #Finds the average of the scores | |
102 | |
103 for i in scores: | |
104 sum += i | |
105 num += 1 | |
106 average = sum/num | |
107 | |
108 #Finds the variance of the scores | |
109 | |
110 xminusxbars = 0 | |
111 for i in scores: | |
112 xminusxbars += (i - average)**2 | |
113 variance = xminusxbars/(num-1) | |
114 | |
115 #Finds the standard deviation and standard error of the scores; then the average / variance / standard deviation / standard error are returned | |
116 | |
117 sd = math.sqrt(variance) | |
118 se = sd / math.sqrt(num) | |
119 return (average, variance, sd, se) | |
120 | |
121 #A subroutine that calculates the weighted average, variance, standard deviation (sd), and standard error (se) of a group of scores; the weights come from the number of reads each insertion location has | |
122 #For use when aggregating scores by gene later on, if the weighted argument is called | |
123 | |
124 def weighted_average(scores,weights): | |
125 sum = 0 | |
126 weighted_average = 0 | |
127 weighted_variance = 0 | |
128 top = 0 | |
129 bottom = 0 | |
130 i = 0 | |
131 | |
132 #Finds weighted average of the scores | |
133 | |
134 while i < len(weights): | |
135 if not scores[i]: | |
136 scores[i] = 0.0 | |
137 top += float(weights[i])*float(scores[i]) | |
138 bottom += float(weights[i]) | |
139 i += 1 | |
140 if bottom == 0: | |
141 return 0 | |
142 weighted_average = top/bottom | |
143 | |
144 #Finds weighted variance of the scores | |
145 | |
146 top = 0 | |
147 bottom = 0 | |
148 i = 0 | |
149 while i < len(weights): | |
150 top += float(weights[i]) * (float(scores[i]) - weighted_average)**2 | |
151 bottom += float(weights[i]) | |
152 i += 1 | |
153 weighted_variance = top/bottom | |
154 | |
155 #Finds weighted standard deviation and standard error of the scores; then the weighted average / variance / standard deviation / standard error are returned | |
156 | |
157 weighted_stdev = math.sqrt(weighted_variance) | |
158 weighted_stder = weighted_stdev/math.sqrt(len(scores)) | |
159 return (weighted_average, weighted_variance, weighted_stdev, weighted_stder) | |
160 | |
161 | |
162 | |
163 | |
164 | |
165 | |
166 | |
167 | |
168 | |
169 | |
170 ##### AGGREGATION / CALCULATIONS ##### | |
171 | |
172 #Reads the genes which should be marked in the final aggregate file into an array | |
173 | |
174 import os.path | |
175 if arguments.marked: | |
176 with open(arguments.marked) as file: | |
177 marked_set = file.read().splitlines() | |
178 | |
179 #Creates a dictionary of dictionaries to contain a summary of all genes and their fitness values | |
180 #Each gene is its own dictionary with w and s as keys for the various fitnesses and weights of the insertion locations within those genes respectively | |
181 #The fitness values and weights match up, so that the weight of gene_summary[locus]["w"][2] would be gene_summary[locus]["s"][2] | |
182 | |
183 import csv | |
184 gene_summary = {} | |
185 for eachfile in arguments.fitnessfiles: | |
186 with open(eachfile) as csvfile: | |
187 lines = csv.reader(csvfile) | |
188 for line in lines: | |
189 locus = line[9] | |
190 w = line[12] | |
191 if w == 'nW': | |
192 continue | |
193 if not w: | |
194 w == 0 | |
195 c1 = float(line[2]) | |
196 c2 = float(line[3]) | |
197 avg = (c1+c2)/2 | |
198 if avg < float(arguments.cutoff): | |
199 continue | |
200 if avg > float(arguments.weight_ceiling): | |
201 avg = arguments.weight_ceiling | |
202 if locus not in gene_summary: | |
203 gene_summary[locus] = {"w" : [], "s": []} | |
204 gene_summary[locus]["w"].append(w) | |
205 gene_summary[locus]["s"].append(avg) | |
206 | |
207 #If finding any missing gene loci is requested in the arguments, starts out by loading all the known features from a genbank file | |
208 | |
209 from Bio import SeqIO | |
210 if (arguments.find_missing): | |
211 output = [["locus","mean","var","sd","se","gene","Total","Blank","Not Blank","Blank Removed","M\n"]] | |
212 handle = open(arguments.find_missing, "rU") | |
213 for record in SeqIO.parse(handle, "genbank"): | |
214 refname = record.id | |
215 features = record.features | |
216 handle.close() | |
217 | |
218 #Goes through the features to find which are genes | |
219 | |
220 for feature in features: | |
221 gene = "" | |
222 if feature.type == "gene": | |
223 locus = "".join(feature.qualifiers["locus_tag"]) | |
224 if "gene" in feature.qualifiers: | |
225 gene = "".join(feature.qualifiers["gene"]) | |
226 else: | |
227 continue | |
228 | |
229 #Goes through the fitness scores of insertions within each gene, and removes whatever % of blank fitness scores were requested along with their corresponding weights | |
230 | |
231 sum = 0 | |
232 num = 0 | |
233 avgsum = 0 | |
234 blank_ws = 0 | |
235 i = 0 | |
236 if locus in gene_summary.keys(): | |
237 for w in gene_summary[locus]["w"]: | |
238 if float(w) == 0: | |
239 blank_ws += 1 | |
240 else: | |
241 sum += float(w) | |
242 num += 1 | |
243 count = num + blank_ws | |
244 removed = 0 | |
245 to_remove = int(float(arguments.blank_pc)*count) | |
246 if blank_ws > 0: | |
247 i = 0 | |
248 while i < len(gene_summary[locus]["w"]): | |
249 if removed == to_remove: | |
250 break | |
251 if not w: | |
252 del gene_summary[locus]["w"][i] | |
253 del gene_summary[locus]["s"][i] | |
254 removed += 1 | |
255 i -= 1 | |
256 i += 1 | |
257 | |
258 #If all the fitness values within a gene are empty, sets mean/var to 0.10 and Xs out sd/se; marks the gene if that's requested | |
259 | |
260 if num == 0: | |
261 if (arguments.marked and locus in marked_set): | |
262 output.append([locus, "0.10", "0.10", "X", "X", gene, count, blank_ws, num, removed, "M", "\n"]) | |
263 else: | |
264 output.append([locus, "0.10", "0.10", "X", "X", gene, count, blank_ws, num, removed, "\n"]) | |
265 | |
266 #Otherwise calls average() or weighted_average() to find the aggregate w / count / standard deviation / standard error of the insertions within each gene; marks the gene if that's requested | |
267 | |
268 else: | |
269 if not arguments.weighted: | |
270 (average, variance, stdev, stderr) = average(gene_summary[locus]["w"]) | |
271 else: | |
272 (average, variance, stdev, stderr) = weighted_average(gene_summary[locus]["w"],gene_summary[locus]["s"]) | |
273 if (arguments.marked and locus in marked_set): | |
274 output.append([locus, average, variance, stdev, stderr, gene, count, blank_ws, num, removed, "M", "\n"]) | |
275 else: | |
276 output.append([locus, average, variance, stdev, stderr, gene, count, blank_ws, num, removed, "\n"]) | |
277 | |
278 #If a gene doesn't have any insertions, sets mean/var to 0.10 and Xs out sd/se, plus leaves count through removed blank because there were no reads. | |
279 | |
280 else: | |
281 if (arguments.marked and locus in marked_set): | |
282 output.append([locus, "0.10", "0.10", "X", "X", gene, "", "", "", "", "M", "\n"]) | |
283 else: | |
284 output.append([locus, "0.10", "0.10", "X", "X", gene, "", "", "", "", "\n"]) | |
285 | |
286 #Writes the aggregated fitness file | |
287 | |
288 with open(arguments.summary, "wb") as csvfile: | |
289 writer = csv.writer(csvfile) | |
290 writer.writerows(output) | |
291 | |
292 #If finding missing genes is not requested, just finds the aggregate w / count / standard deviation / standard error of the insertions within each gene, and writes them to a file, plus marks the genes requested | |
293 #This is never called through Galaxy since finding missing genes is better than not finding them. | |
294 | |
295 else: | |
296 output = [["Locus","W","Count","SD","SE","M\n"]] | |
297 for gene in gene_summary.keys(): | |
298 sum = 0 | |
299 num = 0 | |
300 average = 0 | |
301 if "w" not in gene_summary[gene]: | |
302 continue | |
303 for i in gene_summary[gene]["w"]: | |
304 sum += i | |
305 num += 1 | |
306 average = sum/num | |
307 xminusxbars = 0 | |
308 for i in w: | |
309 xminusxbars += (i-average)**2 | |
310 if num > 1: | |
311 sd = math.sqrt(xminusxbars/(num-1)) | |
312 se = sd / math.sqrt(num) | |
313 if (arguments.marked and locus in marked_set): | |
314 output.append([gene, average, num, sd, se, "M", "\n"]) | |
315 else: | |
316 output.append([gene, average, num, sd, se, "\n"]) | |
317 with open(arguments.summary, "wb") as csvfile: | |
318 writer = csv.writer(csvfile) | |
319 writer.writerows(output) | |
320 | |
321 | |
322 #Test: python ../script/aggregate.py -m tigr4_normal.txt -w 1 -x 10 -l 50 -b 0 -c NC_003028b2.gbk -o aggregates/L3_2394eVI_GlucTEST.csv results/L3_2394eVI_Gluc.csv | |
323 | |
324 #Perl Test: perl ../script/aggregate.pl -m tigr4_normal.txt -w 1 -x 10 -l 50 -b 0 -c NC_003028b2.gbk -o aggregates/L3_2394eVI_Gluc.csv results/L3_2394eVI_Gluc.csv |