Pipeline for detecting recurrent sequence evolution between pairs of duplicated genes

public public 8mo ago 0 bookmarks

#README file to RecurrentEvolution workflow, by S.H.A. von der Dunk (2019); see the original paper (https://bmcecolevol.biomedcentral.com/articles/10.1186/s12862-020-01660-1).

The programme is currently a snakemake workflow comprised of several small scripts. In the Data directory we included two input files as an example that allow you to run through the entire workflow by running "snakemake PTHR11822.out". Snakemake is a programme for building pipelines that works by making through specified rules the requested output-file (PTHR11822.out) if it can (i.e. given it has the input files PTHR11822.indies and PTHR11822.mafft). We refer to the Snakemake manuals for the details: https://snakemake.readthedocs.io/en/stable/

####################

Installation

####################

In our case, the following steps had to be undertaken before the pipeline could be run on a private laptop with Ubuntu.

  1. install miniconda using the website instructions.

  2. install snakemake (with python 3.7) through conda.

  3. install igraph through miniconda ("conda install -c r r-igraph")

  4. install MCL from within R, installing it into the R-lib of miniconda: install.packages("MCL", "/home/sam/miniconda3/lib/R/library") [the latter path should obviously be substituted by the correct path in your own computer]

#################

Execution

#################

See below for Installation. Inside the main directory ("RecurrentEvolution") run the following command in the terminal (the input files for PTHR11822 are provided as an example):

snakemake Output/PTHR11822.out

To have the whole workflow run snakemake on multiple families and with 6 cores:

snakemake -j 6 Output/FAM1.out Output/FAM2.out Output/FAM3.out

Before running the actual pipeline it can be handy to check if snakemake can find the right input files; just add -np to the command you are going to run:

snakemake -np -j 6 Output/FAM1.out Output/FAM2.out Output/FAM3.out

During the workflow, files always contain the family name in their name. Thus, one can in principle run the pipeline for multiple different families at the same time. However, we want to warn that snakemake tends to become REALLY inefficient when the number of jobs becomes very big. So, if you also like to do many bootstraps for each family, snakemake might become the biggest blockade (just run one family at a time, or call snakemake independently for each). Normal execution can be performed by typing "snakemake PTHR11822.out" in the main directory. By default, the number of bootstraps is set to 10. Change this in the Snakefile, under the "Merge Bootstraps" section, to do fewer or more bootstraps. Note that 1 bootstrap is the minimum, otherwise some rules do not get the right number of files for execution. Also note that the bootstraps bring in stochasticity. If different clusters emerge from the bootstrapped alignments, the scores (P and ZF) may also change.

To give an idea of the runtime of the whole workflow from scratch, we timed runs with the PTHR11822 example at a private laptop (excluding the last script): 1 bootstrap - 20s 10 bootstraps - 40s 100 bootstraps - 4m03s

Note that bootstraps generally barely alter the results (e.g. the P- and ZF-score). So when the workflow is used as a first-hand look into a family, one can probably just as well run with a few bootstraps only.

#############

Input

#############

The input files serve as an example for usage of the pipeline.

The current workflow only implements the "clustering of fates" part, since duplications will generally be provided by the user through gene tree reconstruction. The format of [family].indies seems a bit weird, but this stems from the fact that it used to be created by a workflow quite similar to the present pipeline. Thus, to provide inferred duplications of a family, write all species that share a duplication on one line, separated by a tab, and preceeded by some number. Also, the first line will be skipped by the scripts, so you have to write something (e.g. see the "Bootstrap = 100" in PTHR11822.indies).

The other input file is fairly straight-forward; it is the alignment of the family in FASTA format.

##############

Output

##############

After running the example of PTHR11822 (see Execution), 5 files should appear in the Output directory:

  1. PTHR11822.aln: This contains the alignment of only the duplicates that underwent recurrent sequence evolution, grouped by duplications and ordered by fate (i.e. the first of two human paralogs (HSAP) has the same fate as the first of two Dictyostelium paralogs (DDIS)). Open this alignment in JalView.
  2. PTHR11822_groups.jalview: This is one of the two annotation files for PTHR11822.aln in jalview. It draws boxes around duplicates that derive from the same duplication. Upload this file as annotation-file after opening PTHR11822.aln in JalView (note that not all JalView versions work well with annotation-files, in some cases restarting JalView can help).
  3. PTHR11822.out: This shows the two largest predicted fate clusters (same as in Data/PTHR11822.fates) and the two family-level scores for recurrent sequence evolution (see paper); the number of independent duplications that fall completely in these two fates (P) and the average significance of the fate score (ZF) between duplicates in these fates that are not from the same duplication.
  4. PTHR11822_poscol.jalview: This is the second of two annotation files for PTHR11822.aln in JalView. It colors each residue according to its consistency with the overall fate prediction (see paper).
  5. PTHR11822_seqlogo.png: This is a sequence logo that summarizes the consistency of each position with the fate differentiation (i.e. integrating the different colors that PTHR11822_poscol.jalview assigns to residues for an entire column/position in the alignment).

Note that this output is also available for all the families that we analysed in the paper, so for these families it is not necessary to run the analysis again. Check out the Supplementary_Data.zip available from the same location as this present package.

Code Snippets

  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import math, sys, os
import numpy as np

if len(sys.argv) < 4:
	print("Input error.\nUsage: ./cluster_bootstraps.py [output.fates] [originals.fcl] [[bootstraps.fcl]]")
else:
	jaccard_output = sys.argv[1]		#Output for the merged clusters
	real_set = sys.argv[2]				#Clusters from the original data
	comparison_sets = sys.argv[3:]		#Clusters from each of the bootstraps

# Read clusters from original data
with open(real_set, 'r') as fin:
	IndieClusters = []
	for line in fin:
		if len(line)>3:
			species=line.split("\t")
			cluster = []
			for s in species:
				if s != "\n":
					sp = s.strip(" ")
					cluster.append(sp.strip("\n"))
			IndieClusters.append(cluster)

# Read clusters from all bootstrapped data
IndieClusters_comp = [[] for n in range(len(comparison_sets))]
empty_cluster_files = 0
for n in range(len(comparison_sets)):
	with open(comparison_sets[n], 'r') as fin:
		for line in fin:
			if len(line)>3:
				species=line.split("\t")
				cluster = []
				for s in species:
					if s != "\n":
						sp = s.strip(" ")
						cluster.append(sp.strip("\n"))
				IndieClusters_comp[n].append(cluster)

# Find all unique clusters in the ensemble of bootstraps and the original data
AllUniqueClusters = [sorted(cluster) for cluster in IndieClusters]
for bootstrap in IndieClusters_comp:
	for cluster in bootstrap:
		if sorted(cluster) not in AllUniqueClusters:
			AllUniqueClusters.append(sorted(cluster))

# Define Jaccard overlap between two sets
def Jaccard(set_A, set_B):
	return len(set_A.intersection(set_B))/float(len(set_A.union(set_B)))

# Calculate the jaccard congruence of each unique cluster over all bootstrap clusterings.
# In other words, for each unique cluster find its best matching cluster in every bootstrap clustering, and average these jaccard scores.
# Last line makes sure to only include values higher than zero, because a zero indicates that the genes of the unique clusters were absent from the particular clustering file. Generally this means that a clustering file was completely empty, in which case the first line of the output file should inform the user (i.e. "#Bootstraps = 99").
IndieJaccards = []
for cluster in AllUniqueClusters:
	IndieJacs = []
	for bootstrap in IndieClusters_comp:
		max_jaccard = 0.0
		for try_comparison_cluster in bootstrap:		#Compare the unique cluster to each bootstrap clustering
			jac = Jaccard(set(cluster), set(try_comparison_cluster))
			if jac > max_jaccard:
				max_jaccard = jac
		IndieJacs.append(max_jaccard)
	max_jaccard = 0.0
	for try_comparison_cluster in IndieClusters:		#Compare the unique cluster to the original clustering
		jac = Jaccard(set(cluster), set(try_comparison_cluster))
		if jac > max_jaccard:
			max_jaccard = jac
	IndieJacs.append(max_jaccard)
	del IndieJacs[IndieJacs.index(1.0)]
	IndieJaccards.append(round(np.average([x for x in IndieJacs if x != 0.0]),3))

# Simple heuristic to pick the unique clusters starting with the one with the highest jaccard congruence, and then picking the next best cluster that does not have any genes that are already in the first cluster, etcetera.
JaccardSorted_Clusters = [x for _,x in sorted(zip(IndieJaccards, AllUniqueClusters))]
JaccardSorted_Clusters.reverse()
ChooseIndies = []
ChooseJaccard = []
UsedSpecies = []
for cluster in JaccardSorted_Clusters:
	species_double = 0
	for species in cluster:
		if species in UsedSpecies:
			species_double = 1
			break
	if species_double == 0:
		ChooseIndies.append(cluster)
		ChooseJaccard.append(sorted(IndieJaccards)[len(JaccardSorted_Clusters)-JaccardSorted_Clusters.index(cluster)-1])
		UsedSpecies.extend([species for species in cluster])

# Below are commented out some outputs that can give the user an idea of the congruence of the clusters throughout bootstraps

# Print all clusters found anywhere along with there averaged jaccard coefficients
# Note that sometimes the species can be the same, but that two paralogs of a species have swapped places in clusters
"""
for x,y in zip(JaccardSorted_Clusters[::2], reversed(sorted(IndieJaccards)[::2])):
 	print str(y)+"\t"+str(x)

# Print for each cluster found only in the real data, its members and its jaccard coefficient
for x,y in zip(JaccardSorted_Clusters, reversed(sorted(IndieJaccards))):
 	if x in IndieClusters:
 		print str(y)+"\t"+str(x)

# Print in a greedy way the best clusters according to their jaccard coefficients. This is the approach we use in the pipeline.
for x,y in zip(ChooseIndies, ChooseJaccard):
 	print str(y)+"\t"+str(x)
"""

with open(jaccard_output, 'w') as fout:
	fout.write("#Bootstraps = "+str(len(comparison_sets)-empty_cluster_files)+"\n")
	for x,y in zip(ChooseIndies, ChooseJaccard):
		fout.write(str(y)+"\t"+"\t".join([z for z in x])+"\n")
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
import math, sys, os

if len(sys.argv) < 3:
	print("Input error.\nUsage: ./count_sequence_patterns.py [duplicates.tcg] [counts.pts]")
	sys.exit(1)
else:
	alignment = sys.argv[1]
	data_output = sys.argv[2]

#Put labels and sequences in dictionary
seqd = dict()
with open(alignment, 'r') as fin:
	for line in fin:
		if ">" in line:
			current_key = line[1:-1]
			current_key = current_key.partition(" ")[0]
		else:
			if current_key in seqd.keys():
				seqd.update({current_key:seqd[current_key]+line[:-1]})
			else:
				seqd.update({current_key:line[:-1]})

countx = 0
#Start doing species X vs species Y.
for x in sorted(seqd.keys()):
	countx += 1
	#We can skip every other X, because we do comparisons per species not per duplicate.
	if countx % 2 == 0:
		continue
	else:
		county = 0
		for y in sorted(seqd.keys()):
			county += 1
			#Same for Y; skip redundant comparisons.
			if county % 2 == 0:
				continue
			else:
				#X and Y cannot be the same species.
				if y[:4] == x[:4]:
					continue

				#Now find the duplicate of our current x and y.
				else:
					x1_label = x
					x1 = seqd[x]
					x2_label = [n for n in seqd.keys() if (n[:4]==x[:4] and n!=x)][0]
					x2 = seqd[x2_label]
					y1_label = y
					y1 = seqd[y]
					y2_label = [n for n in seqd.keys() if (n[:4]==y[:4] and n!=y)][0]
					y2 = seqd[y2_label]

				#We're counting all possible patterns
				OOOO = 0
				OOOA = 0
				OOAO = 0
				OOAA = 0
				OOAB = 0
				OAOO = 0
				OAOA = 0
				OAOB = 0
				OAAO = 0
				OABO = 0
				OAAA = 0
				OAAB = 0
				OABA = 0
				OABB = 0
				OABC = 0
				AOOO = 0
				AOOA = 0
				AOOB = 0
				AOAO = 0
				AOBO = 0
				AOAA = 0
				AOAB = 0
				AOBA = 0
				AOBB = 0
				AOBC = 0
				AAOO = 0
				ABOO = 0
				AAOA = 0
				AAOB = 0
				ABOA = 0
				ABOB = 0
				ABOC = 0
				AAAO = 0
				AABO = 0
				ABAO = 0
				ABBO = 0
				ABCO = 0
				AAAA = 0
				AAAB = 0
				AABA = 0
				AABB = 0
				AABC = 0
				ABAA = 0
				ABAB = 0
				ABAC = 0
				ABBA = 0
				ABCA = 0
				ABBB = 0
				ABBC = 0
				ABCB = 0
				ABCC = 0
				ABCD = 0


				for px1, px2, px3, px4 in zip(x1,x2,y1,y2):
					if px1 == '-':
						if px2 == '-':
							if px3 == '-':
								if px4 == '-':									# ---- #
									OOOO += 1
								elif px4 != '-':								# ---A #
									OOOA += 1

							elif px3 != '-':
								if px4 == '-':									# --A- #
									OOAO += 1
								elif px4 != '-':
									if px3 == px4:								# --AA #
										OOAA += 1
									elif px3 != px4:							# --AB #
										OOAB += 1
						elif px2 != '-':
							if px3 == '-':
								if px4 == '-':									# -A-- #
									OAOO += 1
								elif px4 != '-':
									if px2 == px4:								# -A-A #
										OAOA += 1
									elif px2 != px4:							# -A-B #
										OAOB += 1
							elif px3 != '-':
								if px4 == '-':
									if px2 == px3:								# -AA- #
										OAAO += 1
									elif px2 != px3:							# -AB- #
										OABO += 1
								elif px4 != '-':							## -xxx ##
									if px2 == px3:
										if px2 == px4:							# -AAA #
											OAAA += 1
										elif px2 != px4:						# -AAB #
											OAAB += 1
									elif px2 != px3:
										if px2 == px4:							# -ABA #
											OABA += 1
										elif px2 != px4:
											if px3 == px4:						# -ABB #
												OABB += 1
											elif px3 != px4:					# -ABC #
												OABC += 1
					elif px1 != '-':
						if px2 == '-':
							if px3 == '-':
								if px4 == '-':
									AOOO += 1								# A--- #
								elif px4 != '-':
									if px1 == px4:								# A--A #
										AOOA += 1
									elif px1 != px4:							# A--B #
										AOOB += 1
							elif px3 != '-':
								if px4 == '-':
									if px1 == px3:								# A-A- #
										AOAO += 1
									elif px1 != px3:							# A-B- #
										AOBO += 1
								elif px4 != '-':							## x-xx ##
									if px1 == px3:
										if px1 == px4:							# A-AA #
											AOAA += 1
										elif px1 != px4:						# A-AB #
											AOAB += 1
									elif px1 != px3:
										if px1 == px4:							# A-BA #
											AOBA += 1
										elif px1 != px4:
											if px3 == px4:						# A-BB #
												AOBB += 1
											elif px3 != px4:					# A-BC #
												AOBC += 1
						elif px2 != '-':
							if px3 == '-':
								if px4 == '-':
									if px1 == px2:								# AA-- #
										AAOO += 1
									elif px1 != px2:							# AB-- #
										ABOO += 1
								elif px4 != '-':							## xx-x ##
									if px1 == px2:
										if px1 == px4:							# AA-A #
											AAOA += 1
										elif px1 != px4:						# AA-B #
											AAOB += 1
									elif px1 != px2:
										if px1 == px4:							# AB-A #
											ABOA += 1
										elif px1 != px4:
											if px2 == px4:						# AB-B #
												ABOB += 1
											elif px2 != px4:					# AB-C #
												ABOC += 1
							elif px3 != '-':
								if px4 == '-':								## xxx- ##
									if px1 == px2:
										if px1 == px3:							# AAA- #
											AAAO += 1
										elif px1 != px3:						# AAB- #
											AABO += 1
									elif px1 != px2:
										if px1 == px3:							# ABA- #
											ABAO += 1
										elif px1 != px3:
											if px2 == px3:						# ABB- #
												ABBO += 1
											elif px2 != px3:					# ABC- #
												ABCO += 1
								elif px4 != '-':							## xxxx ##
									if px1 == px2:
										if px1 == px3:
											if px1 == px4:						# AAAA #
												AAAA += 1
											elif px1 != px4:					# AAAB #
												AAAB += 1
										elif px1 != px3:
											if px1 == px4:						# AABA #
												AABA += 1
											elif px1 != px4:
												if px3 == px4:					# AABB #
													AABB += 1
												elif px3 != px4:				# AABC #
													AABC += 1
									elif px1 != px2:
										if px1 == px3:
											if px1 == px4:						# ABAA #
												ABAA += 1
											elif px1 != px4:
												if px2 == px4:					# ABAB #
													ABAB += 1
												elif px2 != px4:				# ABAC #
													ABAC += 1
										elif px1 != px3:
											if px1 == px4:
												if px2 == px3:					# ABBA #
													ABBA += 1
												if px2 != px3:					# ABCA #
													ABCA += 1
											elif px1 != px4:
												if px2 == px3:
													if px2 == px4:				# ABBB #
														ABBB += 1
													elif px2 != px4:			# ABBC #
														ABBC += 1
												elif px2 != px3:
													if px2 == px4:				# ABCB #
														ABCB += 1
													elif px2 != px4:
														if px3 == px4:			# ABCC #
															ABCC += 1
														elif px3 != px4:		# ABCD #
															ABCD += 1

				with open(data_output,'a') as fout:
					fout.write(x1_label+" "+x2_label+" "+y1_label+" "+y2_label+" "+str(OOOO)+" "+str(OOOA)+" "+str(OOAO)+" "+str(OOAA)+" "+str(OOAB)+" "+str(OAOO)+" "+str(OAOA)+" "+str(OAOB)+" "+str(OAAO)+" "+str(OABO)+" "+str(OAAA)+" "+str(OAAB)+" "+str(OABA)+" "+str(OABB)+" "+str(OABC)+" "+str(AOOO)+" "+str(AOOA)+" "+str(AOOB)+" "+str(AOAO)+" "+str(AOBO)+" "+str(AOAA)+" "+str(AOAB)+" "+str(AOBA)+" "+str(AOBB)+" "+str(AOBC)+" "+str(AAOO)+" "+str(ABOO)+" "+str(AAOA)+" "+str(AAOB)+" "+str(ABOA)+" "+str(ABOB)+" "+str(ABOC)+" "+str(AAAO)+" "+str(AABO)+" "+str(ABAO)+" "+str(ABBO)+" "+str(ABCO)+" "+str(AAAA)+" "+str(AAAB)+" "+str(AABA)+" "+str(AABB)+" "+str(AABC)+" "+str(ABAA)+" "+str(ABAB)+" "+str(ABAC)+" "+str(ABBA)+" "+str(ABCA)+" "+str(ABBB)+" "+str(ABBC)+" "+str(ABCB)+" "+str(ABCC)+" "+str(ABCD)+"\n")
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
CLUSTER_FILE=$1
FCLUSTER_FILE=$2
TCG_ALIGNMENT=$3
SCORE_FILE=$4

# Output
NON_REC_PAIRS_CAT1=$5	#These pairs are from the same duplication and same fate
NON_REC_PAIRS_CAT2=$6	#These pairs are from different dups and different fates
REC_PAIRS=$7

CLUSTERS=`tail -n+2 $CLUSTER_FILE | cut -f2-`
IFS=$'\n'
CLUSTER_ONE_COUNTER=0
for CLUSTER_ONE in $CLUSTERS;
do
	#Check that this cluster is not broken up in the fate clustering
	PREV_SP_POS=0
	IFS=$'\t'
	for SPECIES in $CLUSTER_ONE;
	do
		NEXT_SP_POS=`grep -n $SPECIES $FCLUSTER_FILE | head -1 | cut -d ":" -f1`
		if [ "$NEXT_SP_POS" != "$PREV_SP_POS" ] && [ "$PREV_SP_POS" != "0" ]; then	#Then we will not use this cluster to make pairs
			continue 2
		fi
		PREV_SP_POS=$NEXT_SP_POS
	done

	SPECIES_ONE_COUNTER=0
	for SPECIES_ONE in $CLUSTER_ONE;
	do
		SPECIES_ONE_COUNTER=$((SPECIES_ONE_COUNTER+1))
		SPECIES_TWO_COUNTER=0
		for SPECIES_TWO in $CLUSTER_ONE;
		do
			SPECIES_TWO_COUNTER=$((SPECIES_TWO_COUNTER+1))
			if [ "$SPECIES_TWO_COUNTER" -gt "$SPECIES_ONE_COUNTER" ]; then #We only consider the upper triangle matrix
				#Get the gene names corresponding to species_one and species_two
				GENES=`egrep ">$SPECIES_ONE|>$SPECIES_TWO" $TCG_ALIGNMENT | cut -d " " -f1 | tr "\n" "\t"`
				TEST_SAME_FATE=`tail -n+2 $FCLUSTER_FILE | cut -f2- | egrep -c "$SPECIES_ONE|$SPECIES_TWO"`
				if [ "$TEST_SAME_FATE" == "2" ]; then
					echo -en ${CLUSTER_FILE::-7}"\t"$GENES"\n" >> $NON_REC_PAIRS_CAT1
				fi
			fi
		done
	done
	IFS=$'\n'

	CLUSTER_ONE_COUNTER=$((CLUSTER_ONE_COUNTER+1))
	CLUSTER_TWO_COUNTER=0
	for CLUSTER_TWO in $CLUSTERS;
	do
		#Check that this cluster is not broken up in the fate clustering
		PREV_SP_POS=0
		IFS=$'\t'
		for SPECIES in $CLUSTER_TWO;
		do
			NEXT_SP_POS=`grep -n $SPECIES $FCLUSTER_FILE | head -1 | cut -d ":" -f1`
			if [ "$NEXT_SP_POS" != "$PREV_SP_POS" ] && [ "$PREV_SP_POS" != "0" ]; then	#Then we will not use this cluster to make pairs
				continue 2
			fi
			PREV_SP_POS=$NEXT_SP_POS
		done

		CLUSTER_TWO_COUNTER=$((CLUSTER_TWO_COUNTER+1))
		if [ "$CLUSTER_TWO_COUNTER" -gt "$CLUSTER_ONE_COUNTER" ]; then #We only consider the upper triangle matrix
			#We now want all pairs between cluster_one and cluster_two
			IFS=$'\t'
			for SPECIES_ONE in $CLUSTER_ONE;
			do
				for SPECIES_TWO in $CLUSTER_TWO;
				do
					#Get the gene names corresponding to species_one and species_two
					GENES=`egrep ">$SPECIES_ONE|>$SPECIES_TWO" $TCG_ALIGNMENT | cut -d " " -f1 | tr "\n" "\t"`
					TEST_SAME_FATE=`tail -n+2 $FCLUSTER_FILE | cut -f2- | egrep -c "$SPECIES_ONE|$SPECIES_TWO"`
					if [ "$TEST_SAME_FATE" == "2" ]; then
						#If we only retrieve 2 fates we have to make sure that both species are present in the fcluster-file, otherwise these 2 fates apparently only include one of the species and we cannot say that the genes are recurrently differentiated.
						TEST_SPECIES_ONE=`tail -n+2 $FCLUSTER_FILE | cut -f2- | egrep -c "$SPECIES_ONE"`
						TEST_SPECIES_TWO=`tail -n+2 $FCLUSTER_FILE | cut -f2- | egrep -c "$SPECIES_TWO"`

						if [ "$TEST_SPECIES_ONE" == "2" ] && [ "$TEST_SPECIES_TWO" == "2" ]; then
							TEST_MAX_FATE_SP_ONE=`head -n-1 $SCORE_FILE | egrep -c "$SPECIES_ONE"`
							TEST_MAX_FATE_SP_TWO=`head -n-1 $SCORE_FILE | egrep -c "$SPECIES_TWO"`
							if [ "$TEST_MAX_FATE_SP_ONE" == "2" ] && [ "$TEST_MAX_FATE_SP_TWO" == "2" ]; then
								echo -en ${CLUSTER_FILE::-7}"\t"$GENES"\n" >> $REC_PAIRS
							fi
						else
							echo -e $CLUSTER_FILE":\tOne of the species is missing in the fclusters-file.\t"${GENES[*]}
						fi
					elif [ "$TEST_SAME_FATE" == "4" ]; then
						echo -en ${CLUSTER_FILE::-7}"\t"$GENES"\n" >> $NON_REC_PAIRS_CAT2
					else
						echo -e $CLUSTER_FILE":\tWarning: weird number of fates found in fclusters-file (perhaps both species are not present in it).\t"${GENES[*]}
					fi
				done
			done
			IFS=$'\n'
		fi
	done
done
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
import math,sys,os
import numpy as np
import matplotlib.pyplot as plt
from decimal import *
from random import shuffle
import subprocess
getcontext().prec = 7	#Decimal precision, don't remember the reason for setting this
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#My functions
def GROUPVALUE(N):
	return 1.0

def FATESCORE(list_in):
	fatescore = 0.0
	save_scores = []
	for i in set(list_in):
		fatescore += GROUPVALUE(list_in.count(i))
		save_scores.append(GROUPVALUE(list_in.count(i)))
	return fatescore

def Shannon_Entropy(list_in):	#Information Content
	H = 0
	if sum(list_in) != 1.0:
		temp_list = [None] * len(list_in)
		for x in range(0,len(list_in)):
			temp_list[x] = list_in[x]/float(sum(list_in))
		list_in = [round(x,3) for x in temp_list]
	for x in range(0,len(list_in)):
		if list_in[x] != 0.0:
			H += list_in[x]*math.log(list_in[x]/(1./len(list_in)),2)
	return -H
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#Receive input-arguments
if len(sys.argv) < 4:
	print("Input error.\nUsage: ./identify_recurrent_patterns.py [alignment.tcg] [groups.indies] [groups.fates] [poscol.jalview] [groups.jalview] [alignment.aln] [seqlogo.png]")
	sys.exit(1)
else:
	alignment = sys.argv[1]
	indie_clusters = sys.argv[2]
	fate_clusters = sys.argv[3]

position_coloring = sys.argv[4]
groups_file = sys.argv[5]
alignment_out = sys.argv[6]
save_logo = sys.argv[7]
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#############################################################################################################################################################################################################
#Put labels and sequences in dictionary (only the ones that are in the highest-scoring fate clusters, see below)
seqd = dict()
with open(alignment, 'r') as fin:
	for line in fin:
		if ">" in line:
			current_key = line[1:-1]
			current_key = current_key.partition(" ")[0]
			current_key = current_key.partition("/")[0]
		else:
			if current_key in seqd.keys():
				seqd.update({current_key:seqd[current_key]+line[:-1]})
			else:
				seqd.update({current_key:line[:-1]})

with open(indie_clusters, 'r') as fin:
	IndieClusters = []
	next(fin)
	for line in fin:
		if len(line)>3:
			species=line.split("\t")
			cluster = []
			if species[0][0] == "0" or species[0][0] == "1":
				start_list = 1
			else:
				start_list = 0
			for s in species[start_list:]:
				if s != "\n":
					cluster.append(s[:4])
			IndieClusters.append(cluster)

with open(fate_clusters, 'r') as fin:
	FateClusters = []
	next(fin)
	for line in fin:
		if len(line)>3:
			fate=line.split("\t")
			cluster = []
			if fate[0][0] == "0" or fate[0][0] == "1":
				start_list = 1
			else:
				start_list = 0
			for f in fate[start_list:]:
				if f != "\n":
					c = f.strip(" ")
					cluster.append(c.strip("\n"))
			FateClusters.append(cluster)

#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#Determine what the best-scoring convergent fate is
lfatescores = []
lfate_indies = []
for Fate in FateClusters:
	Indie_IDs = []
	for Gene in Fate:
		for i in range(0, len(IndieClusters)):
			if Gene[:4] in IndieClusters[i]:
				Indie_IDs.append(i)
	#Now check that only complete indies are in my fate (not just the number of unique Indie_IDs)
	Indie_IDs_complete = Indie_IDs
	for ID in set(Indie_IDs):
		number_ids = len([x for x in Indie_IDs if x==ID])
		if number_ids > len(IndieClusters[ID]):
			print("Error: indie does not contain so many species.\n")
		elif number_ids < len(IndieClusters[ID]):
			Indie_IDs_complete = [x for x in Indie_IDs_complete if x!=ID]
	lfatescores.append(FATESCORE(Indie_IDs_complete))
	lfate_indies.append(Indie_IDs_complete)

count = 0
MaxClusters = []
for fs in lfatescores:
	if fs == max(lfatescores):
		MaxClusters.append(FateClusters[count])
		MaxClusters_Indies = lfate_indies[count]
	count += 1

Included_IndieClusters = []
for i in range(0,len(IndieClusters)):
	if i in set(MaxClusters_Indies):
		Included_IndieClusters.append(IndieClusters[i])

#Remove genes that are not in the highest-scoring fates
name_dict = dict()
for key in list(seqd.keys()):
	if key not in MaxClusters[0] and key not in MaxClusters[1]:
		sequence = seqd[key]
		del seqd[key]
	elif key in MaxClusters[0]:
		sequence = seqd[key]
		del seqd[key]
		seqd.update({key[:4]+"_A":sequence})
		name_dict.update({key[:4]+"_A":key})
	elif key in MaxClusters[1]:
		sequence = seqd[key]
		del seqd[key]
		seqd.update({key[:4]+"_B":sequence})
		name_dict.update({key[:4]+"_B":key})
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#Making an alignment-file of the duplicates that show fate recurrence, duplicates ordered as fate-A, fate-B, etc. and species grouped (sorted) per indie.
with open(alignment_out,'a') as fout:
	for Indie in Included_IndieClusters:
		names = [x for x in seqd.keys() if x[:4] in Indie]
		for seq_name in sorted(names):
			fout.write(">"+name_dict[seq_name]+"\n"+seqd[seq_name]+"\n")
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#Making the grouping file for jalview
with open(groups_file, 'a') as fout:
	fout.write("JALVIEW_ANNOTATION\n")
	count_seqs = 0
	for Indie in Included_IndieClusters:
		number_of_seqs = len([x for x in seqd.keys() if x[:4] in Indie])
		fout.write("SEQUENCE_GROUP\tGROUP_"+str(Included_IndieClusters.index(Indie))+"\t1\t"+str(len(sequence))+"\t"+str(count_seqs+1)+"-"+str(count_seqs+number_of_seqs)+"\n")
		count_seqs += number_of_seqs
	for Indie in Included_IndieClusters:
		fout.write("PROPERTIES\tGROUP_"+str(Included_IndieClusters.index(Indie))+"\toutlineColour=gray\tdisplayBoxes=true\n")
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#Blending with white means that the 'zeros' in the rgb-arrays take on these values: 0, 26, 51, 77, 102, 128, 153, 179, 204, 230, 255 (https://meyerweb.com/eric/tools/color-blend/#FFFFFF:00FFFF:9:rgbd)
with open(position_coloring, 'a') as fout:

	#From cyan to green or blue
	fout.write("cFate_m1.0_0.9_AxxA\t0,255,255|0,0,255|absolute|0.0|1.0\n")
	fout.write("cFate_m1.0_0.9_xAAx\t0,255,255|0,255,0|absolute|0.0|1.0\n")
	fout.write("cFate_m0.9_0.8_AxxA\t26,255,255|26,26,255|absolute|0.0|0.9\n")
	fout.write("cFate_m0.9_0.8_xAAx\t26,255,255|26,255,26|absolute|0.0|0.9\n")
	fout.write("cFate_m0.8_0.7_AxxA\t51,255,255|51,51,255|absolute|0.0|0.8\n")
	fout.write("cFate_m0.8_0.7_xAAx\t51,255,255|51,255,51|absolute|0.0|0.8\n")
	fout.write("cFate_m0.7_0.6_AxxA\t77,255,255|77,77,255|absolute|0.0|0.7\n")
	fout.write("cFate_m0.7_0.6_xAAx\t77,255,255|77,255,77|absolute|0.0|0.7\n")
	fout.write("cFate_m0.6_0.5_AxxA\t102,255,255|102,102,255|absolute|0.0|0.6\n")
	fout.write("cFate_m0.6_0.5_xAAx\t102,255,255|102,255,102|absolute|0.0|0.6\n")
	fout.write("cFate_m0.5_0.4_AxxA\t128,255,255|128,128,255|absolute|0.0|0.5\n")
	fout.write("cFate_m0.5_0.4_xAAx\t128,255,255|128,255,128|absolute|0.0|0.5\n")
	fout.write("cFate_m0.4_0.3_AxxA\t153,255,255|153,153,255|absolute|0.0|0.4\n")
	fout.write("cFate_m0.4_0.3_xAAx\t153,255,255|153,255,153|absolute|0.0|0.4\n")
	fout.write("cFate_m0.3_0.2_AxxA\t179,255,255|179,179,255|absolute|0.0|0.3\n")
	fout.write("cFate_m0.3_0.2_xAAx\t179,255,255|179,255,179|absolute|0.0|0.3\n")
	fout.write("cFate_m0.2_0.1_AxxA\t204,255,255|204,204,255|absolute|0.0|0.2\n")
	fout.write("cFate_m0.2_0.1_xAAx\t204,255,255|204,255,204|absolute|0.0|0.2\n")
	fout.write("cFate_m0.1_0.0_AxxA\t255,255,255|230,230,255|absolute|0.0|0.1\n")
	fout.write("cFate_m0.1_0.0_xAAx\t255,255,255|230,255,230|absolute|0.0|0.1\n")

	#From red to magenta or yellow (check visibility)
	fout.write("Fate_p0.0_0.1_AxAx\t255,255,255|255,230,255|absolute|0.0|0.1\n")
	fout.write("Fate_p0.0_0.1_xAxA\t255,255,255|255,255,230|absolute|0.0|0.1\n")
	fout.write("Fate_p0.1_0.2_AxAx\t255,204,204|255,204,255|absolute|0.0|0.2\n")
	fout.write("Fate_p0.1_0.2_xAxA\t255,204,204|255,255,204|absolute|0.0|0.2\n")
	fout.write("Fate_p0.2_0.3_AxAx\t255,179,179|255,179,255|absolute|0.0|0.3\n")
	fout.write("Fate_p0.2_0.3_xAxA\t255,179,179|255,255,179|absolute|0.0|0.3\n")
	fout.write("Fate_p0.3_0.4_AxAx\t255,153,153|255,153,255|absolute|0.0|0.4\n")
	fout.write("Fate_p0.3_0.4_xAxA\t255,153,153|255,255,153|absolute|0.0|0.4\n")
	fout.write("Fate_p0.4_0.5_AxAx\t255,128,128|255,128,255|absolute|0.0|0.5\n")
	fout.write("Fate_p0.4_0.5_xAxA\t255,128,128|255,255,128|absolute|0.0|0.5\n")
	fout.write("Fate_p0.5_0.6_AxAx\t255,102,102|255,102,255|absolute|0.0|0.6\n")
	fout.write("Fate_p0.5_0.6_xAxA\t255,102,102|255,255,102|absolute|0.0|0.6\n")
	fout.write("Fate_p0.6_0.7_AxAx\t255,77,77|255,77,255|absolute|0.0|0.7\n")
	fout.write("Fate_p0.6_0.7_xAxA\t255,77,77|255,255,77|absolute|0.0|0.7\n")
	fout.write("Fate_p0.7_0.8_AxAx\t255,51,51|255,51,255|absolute|0.0|0.8\n")
	fout.write("Fate_p0.7_0.8_xAxA\t255,51,51|255,255,51|absolute|0.0|0.8\n")
	fout.write("Fate_p0.8_0.9_AxAx\t255,26,26|255,26,255|absolute|0.0|0.9\n")
	fout.write("Fate_p0.8_0.9_xAxA\t255,26,26|255,255,26|absolute|0.0|0.9\n")
	fout.write("Fate_p0.9_1.0_AxAx\t255,0,0|255,0,255|absolute|0.0|1.0\n")
	fout.write("Fate_p0.9_1.0_xAxA\t255,0,0|255,255,0|absolute|0.0|1.0\n")
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
#############################################################################################################################################################################################################
lindie_averaged_position_score = [[None for a in range(7)] for b in range(len(sequence) * len(Included_IndieClusters))]
for indie_x in range(0,len(Included_IndieClusters)):
	countx = 0
	lcolumns_position_score = [[None for a in range(7)] for b in range(len(sequence) * len(Included_IndieClusters[indie_x]))]
	#Start doing species X vs species Y.
	for x in sorted(seqd.keys()):
		indie_counter_x = 0
		for Indie in Included_IndieClusters:
			if x[:4] in Indie:
				break
			indie_counter_x += 1
		if indie_counter_x != indie_x:
			continue

		lindie_position_score = [[None for a in range(7)] for b in range(len(sequence) * (len(Included_IndieClusters)-1))]
		#What indie does the focal species belong to
		indie_counter = 0
		for Indie in Included_IndieClusters:
			if x[:4] in Indie:
				IndieX = indie_counter
			indie_counter += 1
		countx += 1
		#We can skip every other X, because we do comparisons per species not per duplicate.
		if countx % 2 == 0:
			continue
		else:
			county = 0
			compare_indie_counter = 0
			for IndieY in range(0,len(Included_IndieClusters)):
				lposition_score = [None] * len(sequence) * len(Included_IndieClusters[IndieY])	#Note that sequence should still be one of the sequences as it was set in the loop that adjusts the dictionary based on the highest-scoring fates, see above
				indie_member_counter = 0
				if IndieY == IndieX:
					continue
				else:
					for y in sorted(seqd.keys()):
						#Treat other species from the same duplication special (not or differently)
						indie_counter = 0
						for Indie in Included_IndieClusters:
							if y[:4] in Indie:
								break
							indie_counter += 1
						county += 1
						#Do only the species that belong to the indie that we are now comparing to the focal species
						if indie_counter != IndieY:
							continue
						#Same for Y; skip redundant comparisons.
						elif county % 2 == 0:
							continue
						else:
							#Now find the duplicate of our current x and y.
							x1_label = x
							x1 = seqd[x]
							x2_label = [n for n in seqd.keys() if (n[:4]==x[:4] and n!=x)][0]
							x2 = seqd[x2_label]
							y1_label = y
							y1 = seqd[y]
							y2_label = [n for n in seqd.keys() if (n[:4]==y[:4] and n!=y)][0]
							y2 = seqd[y2_label]

							position_counter = 0
							for px1, px2, px3, px4 in zip(x1,x2,y1,y2):
								if px1 == '-':
									if px2 == '-':
										if px3 == '-':
											if px4 == '-':									# ---- #
												Fate = 'OOOO'
											elif px4 != '-':								# ---A #
												Fate = 'OOOA'

										elif px3 != '-':
											if px4 == '-':									# --A- #
												Fate = 'OOAO'
											elif px4 != '-':
												if px3 == px4:								# --AA #
													Fate = 'OOAA'
												elif px3 != px4:							# --AB #
													Fate = 'OOAB'
									elif px2 != '-':
										if px3 == '-':
											if px4 == '-':									# -A-- #
												Fate = 'OAOO'
											elif px4 != '-':
												if px2 == px4:								# -A-A #
													Fate = 'OAOA'
												elif px2 != px4:							# -A-B #
													Fate = 'OAOB'
										elif px3 != '-':
											if px4 == '-':
												if px2 == px3:								# -AA- #
													Fate = 'OAAO'
												elif px2 != px3:							# -AB- #
													Fate = 'OABO'
											elif px4 != '-':							## -xxx ##
												if px2 == px3:
													if px2 == px4:							# -AAA #
														Fate = 'OAAA'
													elif px2 != px4:						# -AAB #
														Fate = 'OAAB'
												elif px2 != px3:
													if px2 == px4:							# -ABA #
														Fate = 'OABA'
													elif px2 != px4:
														if px3 == px4:						# -ABB #
															Fate = 'OABB'
														elif px3 != px4:					# -ABC #
															Fate = 'OABC'
								elif px1 != '-':
									if px2 == '-':
										if px3 == '-':
											if px4 == '-':
												Fate = 'AOOO'								# A--- #
											elif px4 != '-':
												if px1 == px4:								# A--A #
													Fate = 'AOOA'
												elif px1 != px4:							# A--B #
													Fate = 'AOOB'
										elif px3 != '-':
											if px4 == '-':
												if px1 == px3:								# A-A- #
													Fate = 'AOAO'
												elif px1 != px3:							# A-B- #
													Fate = 'AOBO'
											elif px4 != '-':							## x-xx ##
												if px1 == px3:
													if px1 == px4:							# A-AA #
														Fate = 'AOAA'
													elif px1 != px4:						# A-AB #
														Fate = 'AOAB'
												elif px1 != px3:
													if px1 == px4:							# A-BA #
														Fate = 'AOBA'
													elif px1 != px4:
														if px3 == px4:						# A-BB #
															Fate = 'AOBB'
														elif px3 != px4:					# A-BC #
															Fate = 'AOBC'
									elif px2 != '-':
										if px3 == '-':
											if px4 == '-':
												if px1 == px2:								# AA-- #
													Fate = 'AAOO'
												elif px1 != px2:							# AB-- #
													Fate = 'ABOO'
											elif px4 != '-':							## xx-x ##
												if px1 == px2:
													if px1 == px4:							# AA-A #
														Fate = 'AAOA'
													elif px1 != px4:						# AA-B #
														Fate = 'AAOB'
												elif px1 != px2:
													if px1 == px4:							# AB-A #
														Fate = 'ABOA'
													elif px1 != px4:
														if px2 == px4:						# AB-B #
															Fate = 'ABOB'
														elif px2 != px4:					# AB-C #
															Fate = 'ABOC'
										elif px3 != '-':
											if px4 == '-':								## xxx- ##
												if px1 == px2:
													if px1 == px3:							# AAA- #
														Fate = 'AAAO'
													elif px1 != px3:						# AAB- #
														Fate = 'AABO'
												elif px1 != px2:
													if px1 == px3:							# ABA- #
														Fate = 'ABAO'
													elif px1 != px3:
														if px2 == px3:						# ABB- #
															Fate = 'ABBO'
														elif px2 != px3:					# ABC- #
															Fate = 'ABCO'
											elif px4 != '-':							## xxxx ##
												if px1 == px2:
													if px1 == px3:
														if px1 == px4:						# AAAA #
															Fate = 'AAAA'
														elif px1 != px4:					# AAAB #
															Fate = 'AAAB'
													elif px1 != px3:
														if px1 == px4:						# AABA #
															Fate = 'AABA'
														elif px1 != px4:
															if px3 == px4:					# AABB #
																Fate = 'AABB'
															elif px3 != px4:				# AABC #
																Fate = 'AABC'
												elif px1 != px2:
													if px1 == px3:
														if px1 == px4:						# ABAA #
															Fate = 'ABAA'
														elif px1 != px4:
															if px2 == px4:					# ABAB #
																Fate = 'ABAB'
															elif px2 != px4:				# ABAC #
																Fate = 'ABAC'
													elif px1 != px3:
														if px1 == px4:
															if px2 == px3:					# ABBA #
																Fate = 'ABBA'
															if px2 != px3:					# ABCA #
																Fate = 'ABCA'
														elif px1 != px4:
															if px2 == px3:
																if px2 == px4:				# ABBB #
																	Fate = 'ABBB'
																elif px2 != px4:			# ABBC #
																	Fate = 'ABBC'
															elif px2 != px3:
																if px2 == px4:				# ABCB #
																	Fate = 'ABCB'
																elif px2 != px4:
																	if px3 == px4:			# ABCC #
																		Fate = 'ABCC'
																	elif px3 != px4:		# ABCD #
																		Fate = 'ABCD'
								Fate_AxAx = ['AOAO','AOAB','ABAO','ABAC','AOBO']
								Fate_xAxA = ['OAOA','OABA','ABOB','ABCB','OAOB']
								Fate_AxxA = ['AOOA','AOBA','ABOA','ABCA','AOOB']
								Fate_xAAx = ['OAAO','OAAB','ABBO','ABBC','OABO']
								if Fate == 'ABAB':
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 5
								elif Fate == 'ABBA':
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 6
								elif Fate in Fate_AxAx:
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 1
								elif Fate in Fate_xAxA:
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 2
								elif Fate in Fate_AxxA:
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 3
								elif Fate in Fate_xAAx:
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 4
								else:
									lposition_score[indie_member_counter*len(sequence)+position_counter] = 0

								position_counter += 1
							indie_member_counter += 1

				for i in range(0,len(sequence)):
					for j in range(0,7):
						lindie_position_score[compare_indie_counter*len(sequence)+i][j] = lposition_score[i::len(sequence)].count(j)/float(len(lposition_score[i::len(sequence)]))
				compare_indie_counter += 1

		for i in range(0,len(sequence)):
			for j in range(0,7):
				sum_this = 0
				for k in range(0,len(Included_IndieClusters)-1):
					sum_this += lindie_position_score[k*len(sequence)+i][j]
				lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][j] = sum_this/float(len(Included_IndieClusters)-1)
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
		count_non_gaps = 0
		for i in range(0,len(sequence)):

			pRest = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][0],3)
			pAxAx = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][1],3)
			pxAxA = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][2],3)
			pAxxA = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][3],3)
			pxAAx = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][4],3)
			pABAB = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][5],3)
			pABBA = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][6],3)
			Fate = pABAB + pAxAx + pxAxA - pABBA - pAxxA - pxAAx

			if x1[i] != '-':
				count_non_gaps += 1
				if Fate > 0:
					if Fate == 1.0:
						Left_Border = "0.9"
						Right_Border = "1.0"
					else:
						Left_Border = str(float(str(round(Fate,3))[:3]))	#e.g. 0.4 if Fate = 0.4325
						Right_Border = str(float(str(round(Fate,3))[:3])+0.1)	#e.g. 0.5 if Fate = 0.4325
					if pAxAx >= pxAxA:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x1_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tFate_p"+Left_Border+"_"+Right_Border+"_AxAx\t"+str(min(abs(pAxAx-pxAxA),abs(Fate)))+"\n")
					elif pAxAx < pxAxA:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x1_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tFate_p"+Left_Border+"_"+Right_Border+"_xAxA\t"+str(min(abs(pAxAx-pxAxA),abs(Fate)))+"\n")

				elif Fate <= 0:
					Left_Border = str(float(str(round(Fate,3))[1:4])+0.1)	#e.g. 0.5 if Fate = -0.4325
					Right_Border = str(float(str(round(Fate,3))[1:4]))	#e.g. 0.4 if Fate = -0.4325
					if pAxxA >= pxAAx:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x1_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tcFate_m"+Left_Border+"_"+Right_Border+"_AxxA\t"+str(min(abs(pAxxA-pxAAx),abs(Fate)))+"\n")
					elif pAxxA < pxAAx:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x1_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tcFate_m"+Left_Border+"_"+Right_Border+"_xAAx\t"+str(min(abs(pAxxA-pxAAx),abs(Fate)))+"\n")
#############################################################################################################################################################################################################
		count_non_gaps = 0
		for i in range(0,len(sequence)):

			pRest = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][0],3)
			pAxAx = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][1],3)
			pxAxA = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][2],3)
			pAxxA = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][3],3)
			pxAAx = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][4],3)
			pABAB = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][5],3)
			pABBA = round(lcolumns_position_score[(int((countx+1)/2)-1)*len(sequence)+i][6],3)
			Fate = round(pABAB + pAxAx + pxAxA - pABBA - pAxxA - pxAAx, 3)

			if x2[i] != '-':
				count_non_gaps += 1
				if Fate > 0:
					if Fate == 1.0:
						Left_Border = "0.9"
						Right_Border = "1.0"
					else:
						Left_Border = str(float(str(round(Fate,3))[:3]))	#e.g. 0.4 if Fate = 0.4325
						Right_Border = str(float(str(round(Fate,3))[:3])+0.1)	#e.g. 0.5 if Fate = 0.4325
					if pAxAx >= pxAxA:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x2_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tFate_p"+Left_Border+"_"+Right_Border+"_AxAx\t"+str(min(abs(pAxAx-pxAxA),abs(Fate)))+"\n")
					elif pAxAx < pxAxA:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x2_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tFate_p"+Left_Border+"_"+Right_Border+"_xAxA\t"+str(min(abs(pAxAx-pxAxA),abs(Fate)))+"\n")

				elif Fate <= 0:
					Left_Border = str(float(str(round(Fate,3))[1:4])+0.1)	#e.g. 0.5 if Fate = -0.4325
					Right_Border = str(float(str(round(Fate,3))[1:4]))	#e.g. 0.4 if Fate = -0.4325
					if pAxxA >= pxAAx:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x2_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tcFate_m"+Left_Border+"_"+Right_Border+"_AxxA\t"+str(min(abs(pAxxA-pxAAx),abs(Fate)))+"\n")
					elif pAxxA < pxAAx:
						with open(position_coloring, 'a') as fout:
							fout.write("Q\t"+name_dict[x2_label]+"\t-1\t"+str(count_non_gaps)+"\t"+str(count_non_gaps)+"\tcFate_m"+Left_Border+"_"+Right_Border+"_xAAx\t"+str(min(abs(pAxxA-pxAAx),abs(Fate)))+"\n")
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
	#Average position scores per indie
	for i in range(0,len(sequence)):
		for j in range(0,7):
			sum_this = 0
			for k in range(0,len(Included_IndieClusters[indie_x])):
				sum_this += lcolumns_position_score[k*len(sequence)+i][j]
			lindie_averaged_position_score[indie_x*len(sequence)+i][j] = round(sum_this/float(len(Included_IndieClusters[indie_x])),3)
#############################################################################################################################################################################################################

#############################################################################################################################################################################################################
linfo_cont = [[None for a in range(len(sequence))] for b in range(7)]
for i in range(0,len(sequence)):
	p = [None] * 7
	for j in range(0,7):
		sum_this = 0
		for k in range(0,len(Included_IndieClusters)):
			sum_this += lindie_averaged_position_score[k*len(sequence)+i][j]
		p[j] = round(sum_this/float(len(Included_IndieClusters)),3)
	for l in range(0,7):
		linfo_cont[l][i] = round(p[l] * -Shannon_Entropy(p), 3)

lplot_cont = [[] for a in range(7)]
lplot_gaps = []
lplot_sims = []
lplot_conservation = []
lx_axis = []
lx_axis_referenced = []
thresholds = [0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55]
conservation_thresholds = [0.0,0.01,0.05,0.1,0.2]
count_axax = [[0 for b in range(5)] for a in range(10)]
count_xaxa = [[0 for b in range(5)] for a in range(10)]
reference_seq_count = 0
for i in range(0,len(sequence)):
	reference = seqd[list(seqd.keys())[0]]
	if reference[i] != '-':
		reference_seq_count += 1
	gaps_in_duplicates = 0
	for key in seqd.keys():
		dup_seq = seqd[key]
		if dup_seq[i] == '-':
			gaps_in_duplicates += 1
	if float(gaps_in_duplicates)/len(seqd.keys()) > 0.5:
		pass
	else:
		lx_axis.append(i+1)
		lx_axis_referenced.append(reference_seq_count)
		for j in range(7):
			lplot_cont[j].append(linfo_cont[j][i]/2.807)


fig, ax = plt.subplots(1,1,figsize=(15,5))
p1 = ax.bar(range(len(lx_axis)), lplot_cont[2], 1, color='yellow', alpha=1.0, linewidth=0, align="edge")
p7 = ax.bar(range(len(lx_axis)), lplot_cont[5], 1, color='red', alpha=1.0, linewidth=0, bottom=lplot_cont[2], align="edge")
p2 = ax.bar(range(len(lx_axis)), lplot_cont[1], 1, color='magenta', alpha=1.0, linewidth=0, bottom=[x1+x2 for x1, x2 in zip(lplot_cont[2],lplot_cont[5])], align="edge")
p3 = ax.bar(range(len(lx_axis)), lplot_cont[3], 1, color='blue', alpha=1.0, linewidth=0, bottom=[x1+x2+x3 for x1, x2, x3 in zip(lplot_cont[2],lplot_cont[5],lplot_cont[1])], align="edge")
p8 = ax.bar(range(len(lx_axis)), lplot_cont[6], 1, color='cyan', alpha=1.0, linewidth=0, bottom=[x1+x2+x3+x4 for x1, x2, x3, x4 in zip(lplot_cont[2],lplot_cont[5],lplot_cont[1],lplot_cont[3])], align="edge")
p4 = ax.bar(range(len(lx_axis)), lplot_cont[4], 1, color='green', alpha=1.0, linewidth=0, bottom=[x1+x2+x3+x4+x5 for x1, x2, x3, x4, x5 in zip(lplot_cont[2],lplot_cont[5],lplot_cont[1],lplot_cont[3],lplot_cont[6])], align="edge")

ax.set_ylim([0.0,1.0])
plt.yticks([0.0,0.25,0.5,0.75])
ax.set_xlim([0,len(lx_axis)])
ax.yaxis.grid(True)
plt.savefig(save_logo)
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import math,sys,os
import numpy as np
from decimal import *

#Decimal precision, don't remember the reason for setting this
getcontext().prec = 7

if len(sys.argv) < 3:
	print("Input error.\nUsage: make_fate_network.py [counts.pts] [network.fnet]")
	sys.exit(1)
else:
	data = sys.argv[1]
	output_network = sys.argv[2]

#Below the main body of the code. It basically reads in the patterns generated by count_sequence_patterns.py and then calculates network edges from these. One note is that {family}.pts contains all species-vs-species comparisons (the full matrix), whereas we here just infer the half matrix (we track when we can skip lines).

count_lines = 0
with open(data, 'r') as fin:
	for line in fin:
		count_lines += 1
		if count_lines == 1:
			skip_lines = 0
			start = 1
			skipped = 0
			remember = ""
		word=line.split()
		if word[0] != remember and start == 0:
			skip_lines += 1
			skipped = 1
			remember = word[0]
		elif skipped < skip_lines:
			skipped += 1
		else:
			x1_label = word[0]
			x2_label = word[1]
			y1_label = word[2]
			y2_label = word[3]
			OOOO = int(word[4])
			OOOA = int(word[5])
			OOAO = int(word[6])
			OOAA = int(word[7])
			OOAB = int(word[8])
			OAOO = int(word[9])
			OAOA = int(word[10])
			OAOB = int(word[11])
			OAAO = int(word[12])
			OABO = int(word[13])
			OAAA = int(word[14])
			OAAB = int(word[15])
			OABA = int(word[16])
			OABB = int(word[17])
			OABC = int(word[18])
			AOOO = int(word[19])
			AOOA = int(word[20])
			AOOB = int(word[21])
			AOAO = int(word[22])
			AOBO = int(word[23])
			AOAA = int(word[24])
			AOAB = int(word[25])
			AOBA = int(word[26])
			AOBB = int(word[27])
			AOBC = int(word[28])
			AAOO = int(word[29])
			ABOO = int(word[30])
			AAOA = int(word[31])
			AAOB = int(word[32])
			ABOA = int(word[33])
			ABOB = int(word[34])
			ABOC = int(word[35])
			AAAO = int(word[36])
			AABO = int(word[37])
			ABAO = int(word[38])
			ABBO = int(word[39])
			ABCO = int(word[40])
			AAAA = int(word[41])
			AAAB = int(word[42])
			AABA = int(word[43])
			AABB = int(word[44])
			AABC = int(word[45])
			ABAA = int(word[46])
			ABAB = int(word[47])
			ABAC = int(word[48])
			ABBA = int(word[49])
			ABCA = int(word[50])
			ABBB = int(word[51])
			ABBC = int(word[52])
			ABCB = int(word[53])
			ABCC = int(word[54])
			ABCD = int(word[55])
			remember = x1_label
			start = 0

			Fate_ABAB = OAOA+AOAO+AOAB+ABAO+ABAB+ABAC+OABA+ABOB+ABCB+OAOB+AOBO	#Or for awk-usage in bash: 11+23+26+39+48+49+17+35+54+12+24
			Fate_ABBA = OAAO+AOOA+AOBA+ABOA+ABBA+ABCA+OAAB+ABBO+ABBC+OABO+AOOB	#Or for awk-usage in bash: 13+21+27+34+50+51+16+40+53+14+22
			Fate_AABB = OOAA+OOAB+OABB+AOBB+AAOO+ABOO+AAOB+AABO+AABB+AABC+ABCC	#Or for awk-usage in bash: 8+9+18+28+30+31+33+38+45+46+55

			# Warn if no informative positions were found.
			if Fate_ABAB + Fate_ABBA + Fate_AABB == 0:
				print("### Warning: no informative positions in this case.")
				Convergence = 0
				Divergence = 0

			else:
				Convergence = abs(Fate_ABAB-Fate_ABBA)/float(Fate_ABAB+Fate_ABBA+Fate_AABB)
				Divergence = Fate_AABB/float(Fate_ABAB+Fate_ABBA+Fate_AABB)

			if Fate_ABAB > Fate_ABBA:
				with open(output_network, 'a') as fout:
					fout.write(x1_label+" "+str(Convergence)+" "+y1_label+"\n"+x2_label+" "+str(Convergence)+" "+y2_label+"\n")
			elif Fate_ABBA > Fate_ABAB:
				with open(output_network, 'a') as fout:
					fout.write(x1_label+" "+str(Convergence)+" "+y2_label+"\n"+x2_label+" "+str(Convergence)+" "+y1_label+"\n")
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
FAMILY=$1		#Just give the name of the family, not any extension.
SCORE_FILE=$2
FCLUSTER_FILE=$3
PATTERN_FILE=$4

NON_REC_PAIRS_CAT1=$5	#These pairs are from the same duplication and same fate
NON_REC_PAIRS_CAT2=$6	#These pairs are from different dups and different fates
REC_PAIRS=$7

# Ouput
OUTPUT=$8

###
PAIR_CATEGORIES=($NON_REC_PAIRS_CAT1 $NON_REC_PAIRS_CAT2 $REC_PAIRS)
SCORE=`tail -1 $SCORE_FILE | cut -d " " -f3`
PAIRS=`grep -w $FAMILY $REC_PAIRS`

Z_scores=()
F_scores=()
PAIR_COUNT=0
SIG_PAIR_COUNT=0

if [ "$SCORE" == "1.0" ] || [ "$SCORE" == "0.0" ]; then
	echo -e "<ZF> = N/A" >> $OUTPUT
	exit
fi


IFS=$'\n'
for PAIR in $PAIRS;
do
	PAIR_COUNT=$((PAIR_COUNT+1))
	IFS=$' '
	GENES=(`echo $PAIR | cut -f2-`)

	#First check how the genes are ordered
	CHECK_FATE_G13=`egrep "${GENES[0]:1:10}|${GENES[2]:1:10}" $FCLUSTER_FILE | wc -l`
	CHECK_FATE_G14=`egrep "${GENES[0]:1:10}|${GENES[3]:1:10}" $FCLUSTER_FILE | wc -l`

	ORDER="OOOO"
	if [ "$CHECK_FATE_G13" == "1" ] && [ "$CHECK_FATE_G14" == "2" ]; then
		ORDER="ABAB"
	elif [ "$CHECK_FATE_G13" == "2" ] && [ "$CHECK_FATE_G14" == "1" ]; then
		ORDER="ABBA"
	fi

	PATTERNS=`cat $PATTERN_FILE | egrep "${GENES[0]:1:10}" | egrep "${GENES[2]:1:10}" | head -1 | cut -d " " -f5-`
	ABAB=`echo $PATTERNS | awk '{print $7+$19+$22+$35+$44+$45+$13+$31+$50+$8+$20}'`
	ABBA=`echo $PATTERNS | awk '{print $9+$17+$23+$30+$46+$47+$12+$36+$49+$10+$18}'`
	AABB=`echo $PATTERNS | awk '{print $4+$5+$14+$24+$26+$27+$29+$34+$41+$42+$51}'`
	if [ "$ORDER" == "ABBA" ]; then
		TEMP=$ABAB
		ABAB=$ABBA
		ABBA=$TEMP
	fi

	TOTAL=`echo $PATTERNS | awk '{print $1+$2+$3+$4+$5+$6+$7+$8+$9+$10+$11+$12+$13+$14+$15+$16+$17+$18+$19+$20+$21+$22+$23+$24+$25+$26+$27+$28+$29+$30+$31+$32+$33+$34+$35+$36+$37+$38+$39+$40+$41+$42+$43+$44+$45+$46+$47+$48+$49+$50+$51+$52}'`
	dF=`echo $ABAB $ABBA | awk '{print $1-$2}'`
	SEMI_TOTAL=`echo $ABAB $ABBA $AABB | awk '{print $1+$2+$3}'`

	if [ "$SEMI_TOTAL" == "0" ]; then
		F=0
		VdF1=0
		ZF1=0
	else
		F=`echo $ABAB $ABBA $SEMI_TOTAL | awk '{print ($1-$2)/$3}'`
		VdF1=`echo $ABAB $ABBA $SEMI_TOTAL | awk '{print $3*($1/$3)*(1-($1/$3)) + $3*($2/$3)*(1-($2/$3)) + 2*$3*($1/$3)*($2/$3)}'`
		if [ "$VdF1" == "0" ]; then
			ZF1=0
		else
			ZF1=`echo $dF $VdF1 | awk '{print $1/(sqrt($2))}'`
		fi
	fi

	VdF2=`echo $PATTERNS $TOTAL | awk '{print $7" "$19" "$22" "$35" "$44" "$45" "$13" "$31" "$50" "$8" "$20" "$9" "$17" "$23" "$30" "$46" "$47" "$12" "$36" "$49" "$10" "$18" "$53}' | awk '{Vsum=0; for (i=1; i<=22; i++) Vsum+= $23*($i/$23)*(1-($i/$23)); for (i=1; i<=22; i++) for (j=i+1; j<=22; j++) Vsum+= 2*$23*($i/$23)*($j/$23); print Vsum}'`
	if [ "$VdF2" == "0" ]; then
		continue	#Choose to not include this pair, because its Z-score is basically not defined.
	else
		ZF2=`echo $dF $VdF2 | awk '{print $1/(sqrt($2))}'`
	fi

	#ZF2 seems only slightly lower than ZF1 for each pair, so let's use the slightly more conservative ZF2.
	F_scores+=($F)
	Z_scores+=($ZF2)

	if (( $(echo "$ZF2 > 1.96" |bc -l) )); then
		SIG_PAIR_COUNT=$((SIG_PAIR_COUNT+1))
	fi

done

IFS=$' '
MIN_Z=100
MAX_Z=0
for Z in ${Z_scores[*]}; do
	if (( $(echo "$Z > $MAX_Z" |bc -l) )); then
		MAX_Z=$Z
	fi

	if (( $(echo "$Z < $MIN_Z" |bc -l) )); then
		MIN_Z=$Z
	fi
done

MIN_F=100
MAX_F=0
for F in ${F_scores[*]}; do
	if (( $(echo "$F > $MAX_F" |bc -l) )); then
		MAX_F=$F
	fi

	if (( $(echo "$F < $MIN_F" |bc -l) )); then
		MIN_F=$F
	fi
done

Z_LENGTH=${#Z_scores[@]}
if ! (($Z_LENGTH%2)); then
	HALF_Z_LENGTH=$((Z_LENGTH/2))
	HALF_Z_LENGTH=$((HALF_Z_LENGTH+1))
	MEDIAN_Z=`echo ${Z_scores[*]} | tr " " "\n" | sort -n | head -$HALF_Z_LENGTH | tail -2 | awk '{sum+=$1} END {print sum/2}'`
	MEDIAN_F=`echo ${F_scores[*]} | tr " " "\n" | sort -n | head -$HALF_Z_LENGTH | tail -2 | awk '{sum+=$1} END {print sum/2}'`
else
	HALF_Z_LENGTH=$((Z_LENGTH/2))
	HALF_Z_LENGTH=$((HALF_Z_LENGTH+1))
	MEDIAN_Z=`echo ${Z_scores[*]} | tr " " "\n" | sort -n | head -$HALF_Z_LENGTH | tail -1`
	MEDIAN_F=`echo ${F_scores[*]} | tr " " "\n" | sort -n | head -$HALF_Z_LENGTH | tail -1`
fi

echo $PAIR_COUNT $SIG_PAIR_COUNT $MIN_Z $MEDIAN_Z $MAX_Z ${Z_scores[*]} | awk '{sum=0; for (i=6; i<=NF; i++) sum+=$i; printf ("<ZF> = %.3f\t(%i/%i significant pairs)\n", sum/(NF-5), $2, $1)}' >> $OUTPUT
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import math,sys,os
import numpy as np
from decimal import *

#Decimal precision
getcontext().prec = 7

if len(sys.argv) < 4:
	print("Input error.\nUsage: ./measure_recurrence_prevalence.py [groups.indies] [groups.fates] [output.score]")
	sys.exit(1)
else:
	indie_clusters = sys.argv[1]
	fate_clusters = sys.argv[2]
	score_output = sys.argv[3]

def GROUPVALUE(N):
	return 1.0

def FATESCORE(list_in):
	fatescore = 0.0
	save_scores = []
	for i in set(list_in):
		fatescore += GROUPVALUE(list_in.count(i))
		save_scores.append(GROUPVALUE(list_in.count(i)))
	return fatescore

#Import cluster data
with open(indie_clusters, 'r') as fin:
	IndieClusters = []
	next(fin)
	for line in fin:
		if len(line)>3:
			species=line.split("\t")
			cluster = []
			if species[0][0] == "0" or species[0][0] == "1":
				start_list = 1
			else:
				start_list = 0
			for s in species[start_list:]:
				if s != "\n":
					cluster.append(s[:4])
			IndieClusters.append(cluster)

with open(fate_clusters, 'r') as fin:
	FateClusters = []
	next(fin)
	for line in fin:
		if len(line)>3:
			fate=line.split("\t")
			cluster = []
			if fate[0][0] == "0" or fate[0][0] == "1":
				start_list = 1
			else:
				start_list = 0
			for f in fate[start_list:]:
				if f != "\n":
					c = f.strip(" ")
					cluster.append(c.strip("\n"))
			FateClusters.append(cluster)

if len(FateClusters) < 2:
	with open(score_output, 'a') as fout:
		fout.write("P = 0.0\n")
		sys.exit(1)

#Check whether fate clustering is symmetric
spoofFateClusters = []
for Fate in FateClusters:
	spoofFateClusters.append([F[:4] for F in Fate])
for i in range(0, len(spoofFateClusters)):
	spFate1 = spoofFateClusters[i]
	validated = 0
	for j in range(0, len(spoofFateClusters)):
		if j != i:
			spFate2 = spoofFateClusters[j]
			if sorted(spFate1) == sorted(spFate2):
				validated = 1
	if validated == 0:
		print("Warning: fate clusters are not symmetric.\n")
		with open(score_output, 'a') as fout:
			fout.write("P = NA\n")
		sys.exit(1)

#Calculate score
lfatescores = []
for Fate in FateClusters:
	Indie_IDs = []
	for Gene in Fate:
		for i in range(0, len(IndieClusters)):
			if Gene[:4] in IndieClusters[i]:
				Indie_IDs.append(i)
	#Now check that only complete indies are in my fate (not just the number of unique Indie_IDs)
	Indie_IDs_complete = Indie_IDs
	for ID in set(Indie_IDs):
		number_ids = len([x for x in Indie_IDs if x==ID])
		if number_ids > len(IndieClusters[ID]):
			print("Error: indie does not contain so many species.\n")
			with open(score_output, 'a') as fout:
				fout.write("P = NA\n")
			sys.exit(1)
		elif number_ids < len(IndieClusters[ID]):
			Indie_IDs_complete = [x for x in Indie_IDs_complete if x!=ID]
	if len(Indie_IDs_complete) > 0:
		lfatescores.append(FATESCORE(Indie_IDs_complete))
	else:
		lfatescores.append(0.0)

TotalScore = max(lfatescores)

Indie_Splits = 0
for Indie in IndieClusters:
	Fate_IDs = []
	for Species in Indie:
		for i in range(0, len(FateClusters)):
			if Species in [f[:4] for f in FateClusters[i]]:
				Fate_IDs.append(i)
	if len(set(Fate_IDs)) > 2:
		Indie_Splits += (len(set(Fate_IDs))-2)/2

count_fates = 0
with open(score_output, 'a') as fout:
	for s, F in zip(lfatescores, FateClusters):
		if s == TotalScore:
			count_fates += 1
			fout.write("Fate "+str(count_fates)+": "+"\t".join(F)+"\n")
	fout.write("P = "+str(TotalScore)+"\n")
12
13
14
15
16
17
18
19
20
shell:
    """
    set +o pipefail;
    if [ "{wildcards.bootstrap_n}" == "0" ]; then
    	cp Data/{wildcards.family}.mafft Data/{wildcards.family}.mafft_bs:0
    else
    	Codes/generate_bootstrap_alignments.py {input} {wildcards.bootstrap_n}
    fi
    """
32
33
34
35
36
37
38
39
shell:
    """
    set +o pipefail;
    Codes/extract_two_copy_genes.py {input} {output}
    if [ "{wildcards.bootstrap_n}" == "0" ]; then
    	rm -f Data/{wildcards.family}.mafft_bs:0
    fi
    """
51
52
shell:
    "Codes/count_sequence_patterns.py {input} {output}"
66
67
shell:
    "Codes/make_fate_network.py {input} {output}"
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
shell:
    """
    set +o pipefail;
    Codes/cluster_fates.R {input.fate_network} markov temp_{wildcards.bootstrap_n}.fcl 5 10 0.0
    cp temp_{wildcards.bootstrap_n}.fcl {output}
    ALL_TCG_GENES=`cat {input.two_copy_genes} | grep ">" | cut -c2-`
    for GENE in $ALL_TCG_GENES;
    do
        CHECK_PRESENCE=`grep ${{GENE:0:10}} temp_{wildcards.bootstrap_n}.fcl | wc -l`
        if [ "$CHECK_PRESENCE" -lt "1" ]; then
            echo -en $GENE"\n" >> {output}
        fi
    done
    rm -f temp_{wildcards.bootstrap_n}.fcl
    """
110
111
shell:
    "Codes/cluster_bootstraps.py {output} {input.original} {input.bootstraps}"
SnakeMake From line 110 of master/Snakefile
124
125
shell:
    "Codes/measure_recurrence_prevalence.py {input.indies} {input.fates} {output}"
SnakeMake From line 124 of master/Snakefile
142
143
shell:
    "Codes/find_recurrent_quartets.sh {input.indies} {input.fates} {input.two_copy_genes} {input.score} {output.non_rec_pairs1} {output.non_rec_pairs2} {output.recurrent_pairs}"
SnakeMake From line 142 of master/Snakefile
156
157
shell:
    "Codes/measure_recurrence_magnitude.sh {wildcards.family} {input.score} {input.fates} {input.patterns} {input.non_rec_pairs1} {input.non_rec_pairs2} {input.recurrent_pairs} {output}"
SnakeMake From line 156 of master/Snakefile
174
175
shell:
	"Codes/identify_recurrent_patterns.py {input.alignment} {input.indies} {input.fates} {output.position_colors} {output.groups} {output.reordered_alignment} {output.seq_logo}"
SnakeMake From line 174 of master/Snakefile
188
189
190
191
192
193
194
195
shell:
    """
    set +o pipefail;
    cat {input.one} {input.two} > {output}
    rm -f {input.one} {input.two}
    head -1 {input.three} >> check.out
    rm -f check.out
    """
SnakeMake From line 188 of master/Snakefile
ShowHide 16 more snippets with no or duplicated tags.

Free

Created: 8mo ago
Updated: 8mo ago
Maitainers: public
URL: https://github.com/samvonderdunk/RecurrentEvolution
Name: recurrentevolution
Version: 1
Badge:
workflow icon

Insert copied code into your website to add a link to this workflow.

Downloaded: 0
Copyright: Public Domain
License: None
  • Future updates

Related Workflows

cellranger-snakemake-gke
snakemake workflow to run cellranger on a given bucket using gke.
A Snakemake workflow for running cellranger on a given bucket using Google Kubernetes Engine. The usage of this workflow ...