-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathPyBSASeq.py
More file actions
2089 lines (1700 loc) · 98.3 KB
/
Copy pathPyBSASeq.py
File metadata and controls
2089 lines (1700 loc) · 98.3 KB
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
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
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
PyBSASeq, version 3.1415
Created on Fri Oct 5 08:22:16 2018
Updated on
@author: Jianbo Zhang
'''
import os
import sys
import time
import datetime
import argparse
import csv
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import ttest_rel
from scipy.signal import savgol_filter
import ast
try:
from fisher import pvalue_npy
except ImportError:
try:
from scipy.stats import fisher_exact
except ImportError:
print('Either the module \'Fisher\' (https://github.com/brentp/fishers_exact_test) or \'fisher_exact\' from \'scipy.stat\' is needed to performe Fisher\'s exact tests.')
sys.exit()
import statsmodels.api as sm
from statsmodels.stats.stattools import durbin_watson
def sm_allelefreq(pop_struc, bulk_size, rep):
'''
This function is for the calculation of the ALT allele frequency in the bulks via simulation
under the null hypothesis (the SV is not associated with the trait).
An AA, Aa, and aa individual carries 0%, 50%, and 100% of the alt (a) allele, respectively.
If A/a is not associated with the trait (null hypothesis), the AA:Aa:aa ratios are 0.25:0.5:0.25, 0.5:0:0.5,
and 0.5:0.5:0, respectively, in both bulks of an F2 population, an RIL population, or a back crossed population.
'''
freq_list = []
pop = [0.0, 0.5, 1.0]
if pop_struc == 'F2':
prob = [0.25, 0.5, 0.25]
elif pop_struc == 'RIL':
prob = [0.5, 0.0, 0.5]
elif pop_struc == 'BC':
prob = [0.5, 0.5, 0.0]
for __ in range(rep):
alt_freq = np.random.choice(pop, bulk_size, p=prob).mean()
freq_list.append(alt_freq)
return sum(freq_list)/len(freq_list)
def sort_chrm(l):
a, b = [], []
for eml in l:
if eml.isdigit():
a.append(eml)
else:
b.append(eml)
a.sort(key=int)
b.sort()
a.extend(b)
return a
def chrm_filtering(df, chromosome_list):
# Many reference genomes contain unmapped fragments that tend to be small and are not informative for SV-trait association, filtering them out makes the chromosome list more readable.
all_chrm_ids, all_chrm_range, small_chrm_ids = [], [], []
for chrm in chromosome_list:
chrm_size = df[df.CHROM==chrm]['POS'].max().item()
if chrm_size <= min_frag_size:
small_chrm_ids.append(chrm)
else:
all_chrm_ids.append(chrm)
all_chrm_range.append([1, chrm_size]) # Startpoint and endpoint
return [all_chrm_ids, all_chrm_range, small_chrm_ids]
def select_chrms(df, rgn):
# Create a chromosome list, which can be very long because of the unmapped fragments
global chrm_prnt_dict
chrm_prnt_dict = {}
raw_chrm_ids_ori = df['CHROM'].unique().tolist()
raw_chrm_ids = sort_chrm(raw_chrm_ids_ori)
# Filter out chromosomes and unmapped fragments smaller than the sliding window
# Make the chromosome list more readable and meaningful
chrm_id_range = chrm_filtering(df, raw_chrm_ids)
chrm_ids = chrm_id_range[0]
chrm_range = chrm_id_range[1]
small_frags = chrm_id_range[2]
m = 1
for eml in chrm_ids:
chrm_prnt_dict[eml] = str(m)
m += 1
if small_frags != []:
print('\nThe chromosomes/fragments below are filtered out because of their small sizes:')
print(small_frags, '\n')
dsrd_chrm_ids, dsrd_chrm_range, dsrd_chrm_sizes = [], [], []
# Chromosomal region arguments are specified
if rgn[0] == -1:
print(f'The chromosomes below are greater than {min_frag_size} bp and are suitable for BSA-Seq analysis:')
print(chrm_ids,'\n')
print('Although a subset of the above chromosomes can be selected for analysis in next step, it is strongly recommended to have all the chromosomes included when running the script the first time.\n')
if chrm_order == False:
dsrd_chrm_ids = chrm_ids
else:
# while right_input.lower() != 'yes':
# input_string = input('Enter the names of the desired chromosomes for analysis in order and separate each name with a comma, or press the ENTER/RETURN key if the above list is what you want:\n')
# if input_string == '':
# dsrd_chrm_ids = chrm_ids
# else:
# dsrd_chrm_ids = [x.strip() for x in input_string.split(',')]
# print('Invalid chromosome names, if any, will be removed.\n')
# Filter out possible invalid chromosome name(s)
invalid_chrm_list, frags = [], []
for ch in chrm_order:
if ch not in chrm_ids:
invalid_chrm_list.append(ch)
elif ch in small_frags:
frags.append(ch)
else:
dsrd_chrm_ids.append(ch)
if invalid_chrm_list != []:
print('These chromosome names are invalid:', invalid_chrm_list,'\n')
if frags != []:
print(f'The chromosomes below are less than {min_frag_size} bp and are not suitable for BSA-Seq analysis:')
print(frags,'\n')
print('Chromosomes for analysis:')
print(dsrd_chrm_ids,'\n')
# right_input = input('Are the chromosome names in the above list in the right order (yes or no)?\n')
# print('\n')
# Create dictionary/list containing the sizes of all the chromosomes
i = 0
for ch in chrm_ids:
if ch in dsrd_chrm_ids:
temp = chrm_range[i]
dsrd_chrm_range.append(temp)
dsrd_chrm_sizes.append(temp[1])
i += 1
# Handle the cases in which one or more chromosomal regions are specified
else:
l, n = len(rgn), len(rgn) % 3
if n != 0:
rgn = rgn[0:l-n]
print('Each chromosome name should be followed by the starting and ending points of the interested region.')
i = 0
while i < l:
if rgn[i] == 1000:
ch_id = 'X'
elif rgn[i] == 1001:
ch_id = 'Y'
elif rgn[i] == 1002:
ch_id = 'Z'
elif rgn[i] == 1003:
ch_id = 'W'
elif rgn[i] == 1004:
ch_id = 'U'
elif rgn[i] == 1005:
ch_id = 'V'
else:
ch_id = str(rgn[i])
if ch_id not in chrm_ids:
print(ch_id, 'is not a valid chromosome name.')
else:
temp_chrm_size = chrm_range[chrm_ids.index(ch_id)][1]
if rgn[i+1] < 1:
rgn[i+1] = 1
if rgn[i+2] > temp_chrm_size:
rgn[i+2] = temp_chrm_size
if rgn[i+2] - rgn[i+1] > min_frag_size:
dsrd_chrm_ids.append(ch_id)
dsrd_chrm_range.append([rgn[i+1], rgn[i+2]])
dsrd_chrm_sizes.append(rgn[i+2]-rgn[i+1]+1)
else:
print(f'The size of the interested region on chromosome {ch_id} should be greater than ', {min_frag_size}, 'bp.')
i += 3
return [dsrd_chrm_ids, dsrd_chrm_range, dsrd_chrm_sizes]
def bulk_names(df):
global header
header = df.columns.values.tolist()
bulks = []
# Obtain the bulk IDs from the header
# try:
for ftr_name in header:
if ftr_name.endswith('.AD') or ftr_name.endswith('_AD'):
bulks.append(ftr_name.split('.')[0])
return bulks
# except (NameError, IndexError):
# print('The allele depth (AD) field is missing. Please include the AD field in the input file.')
# sys.exit()
def xticks_property(l):
# Automatically adjust xticks
# Calculate distance between xticks and determine the tick unit
lrgst_chrm_length = max(l)
div_list = [500000000, 200000000, 100000000, 50000000, 20000000, 10000000, 5000000, 2000000, 1000000]
max_xticks, min_xticks= 7, 3
if lrgst_chrm_length/div_list[0] > max_xticks:
div_unit = 1000000000
length_unit = 'Gb'
rmzero = 1e-9
elif lrgst_chrm_length/div_list[-1] < min_xticks:
div_unit = 100000
length_unit = '\u00D7100 kb'
rmzero = 1e-5
else:
for i in div_list:
if lrgst_chrm_length/i <= max_xticks and lrgst_chrm_length/i >= min_xticks:
div_unit = i
if i/1000000 >= 100:
length_unit = '\u00D7100 Mb'
rmzero = 1e-8
elif i/1000000 >= 10:
length_unit ='\u00D710 Mb'
rmzero = 1e-7
elif i/1000000 >= 1:
length_unit = 'Mb'
rmzero = 1e-6
break
return [div_unit, rmzero, length_unit]
def sv_filtering(df):
print(f'Perform SV filtering - {sample}')
df = df.copy()
# Identify SVs not informative and remove these SVs from the dataframe
df_ignored = df[~df.CHROM.isin(selected_chrms)]
df_ignored.to_csv(os.path.join(filtering_path, 'ignored.csv'), index=None)
df = df.drop(index=df_ignored.index)
# Identify SVs with an 'NA' value(s) and remove these SVs from the dataframe
df_na = df[df.isnull().any(axis=1)]
df_na.to_csv(os.path.join(filtering_path, 'na.csv'), index=None)
df.dropna(inplace=True)
misc.append(['Number of SVs after NA drop - '+sample_name, len(df.index)])
misc.append([''])
# Identify SVs with more than one ALT allele
df_m_alts = df[df.ALT.str.contains(',')]
# Identify SVs with a single ALT allele
df_1_alt = df.drop(index=df_m_alts.index)
# Identify one-ALT SVs with zero REF read in both bulks
df_1_alt_fake = df_1_alt[(df_1_alt[fb_ad].str.startswith('0')) & (df_1_alt[sb_ad].str.startswith('0'))]
df_1_alt_fake.to_csv(os.path.join(filtering_path, 'fake_1alt.csv'), index=None)
# Remove one-ALT SVs with zero REF read in both bulks
df_1_alt_real = df_1_alt.drop(index=df_1_alt_fake.index)
df_1_alt_real.to_csv(os.path.join(filtering_path, 'real_1alt.csv'), index=None)
# Using 'str.count' make the code below simpler and easier to understand
df_3_alts = df_m_alts[df_m_alts.ALT.str.count(',') >= 2]
df_3_alts.to_csv(os.path.join(filtering_path, '3alts.csv'), index=None)
df_2_alts = df_m_alts.drop(index=df_3_alts.index)
# A two-ALT SV is a real SV if the REF read is zero in both bulks
df_2_alts_real = df_2_alts[(df_2_alts[fb_ad].str.startswith('0')) & (df_2_alts[sb_ad].str.startswith('0'))]
# The two-ALT SV may be caused by allele heterozygosity if the REF read is not zero
# Repetitive sequences in the genome or sequencing artifacts are other possibilities
df_2_alts_het = df_2_alts.drop(index=df_2_alts_real.index)
df_2_alts_het.to_csv(os.path.join(filtering_path, '2alts_het.csv'), index=None)
# Making a copy to suppress the warning message. Updating the AD values of these SVs is required
df_2_alts_real = df_2_alts_real.copy()
df_2_alts_real.to_csv(os.path.join(filtering_path, 'real_2alts_before.csv'), index=None)
# Update the AD values of the above SVs by removing the REF read that is zero (i.e. remove '0,' from '0,x,y')
if not df_2_alts_real.empty:
df_2_alts_real[fb_ad] = df_2_alts_real[fb_ad].str.slice(start=2)
df_2_alts_real[sb_ad] = df_2_alts_real[sb_ad].str.slice(start=2)
df_2_alts_real[['REF', 'ALT']] = df_2_alts_real.ALT.str.split(',', expand=True)
df_2_alts_real.to_csv(os.path.join(filtering_path, 'real_2alts_after.csv'), index=None)
# Concatenate 1ALT_Real and 2ALT_Real
df = pd.concat([df_1_alt_real, df_2_alts_real])
# df = df_1_alt_real.copy()
# Filter out 1 bp deletions
indel_1 = df[(df.REF.str.contains(r'\*')) | (df.ALT.str.contains(r'\*'))]
# df = df.drop(index=indel_1.index)
# Remove InDels from the SV dataframe
indel_2 = df[(df.REF.str.len()>1) | (df.ALT.str.len()>1)]
# df = df.drop(index=indel_2.index)
df_indel = pd.concat([indel_1, indel_2])
df_indel.to_csv(os.path.join(filtering_path, 'indel.csv'), index=None)
# Obtain REF reads, ALT reads, and locus reads of each SV
df[[fb_ad_ref, fb_ad_alt]] = df[fb_ad].str.split(',', expand=True).astype(int)
df[fb_ld] = df[fb_ad_ref] + df[fb_ad_alt]
df[[sb_ad_ref, sb_ad_alt]] = df[sb_ad].str.split(',', expand=True).astype(int)
df[sb_ld] = df[sb_ad_ref] + df[sb_ad_alt]
# Filter out the SVs with zero locus reads in either bulk
sv_0ld = df[(df[fb_ld] == 0) | (df[sb_ld] == 0)]
sv_0ld.to_csv(os.path.join(filtering_path, '0ld.csv'), index=None)
df = df.drop(index=sv_0ld.index)
# Filter out SVs in which GT and AD are not consistent
df[[fb_gt_ref, fb_gt_alt]] = df[fb_gt].str.split('/|\\|', expand=True)
df[[sb_gt_ref, sb_gt_alt]] = df[sb_gt].str.split('/|\\|', expand=True)
gt_ad = df[(df[fb_gt_ref]==df[fb_gt_alt]) & (df[sb_gt_ref]==df[sb_gt_alt]) & (df[fb_gt_ref]==df[sb_gt_ref])]
df = df.drop(index=gt_ad.index)
gt_ad.to_csv(os.path.join(filtering_path, 'gt_ad.csv'), index=None)
# Filter out SVs in which bulk GT and REF/ALT are not consistent
gt = df[~(((df[fb_gt_ref]==df.REF) | (df[fb_gt_ref]==df.ALT)) & \
((df[fb_gt_alt]==df.REF) | (df[fb_gt_alt]==df.ALT)) & \
((df[sb_gt_ref]==df.REF) | (df[sb_gt_ref]==df.ALT)) & \
((df[sb_gt_alt]==df.REF) | (df[sb_gt_alt]==df.ALT)))]
gt.to_csv(os.path.join(filtering_path, 'gt.csv'), index=None)
df = df.drop(index=gt.index)
df.sort_values(['ChrmSortID', 'POS'], inplace=True)
print(f'SV filtering completed, time elapsed: {(time.time()-t0)/60} minutes.')
return df
def read_filter(df):
fb_rep_ld, sb_rep_ld = df[fb_ld].mean() + 3*df[fb_ld].std(), df[sb_ld].mean() + 3*df[sb_ld].std()
df_rep = df[(df[fb_ld]>fb_rep_ld) | (df[sb_ld]>sb_rep_ld)]
df_rep.to_csv(os.path.join(filtering_path, 'repetitive_seq.csv'), index=None)
df = df.drop(index=df_rep.index)
df_lowread = df[(df[fb_ld]<3) | (df[sb_ld]<3)]
df_lowread.to_csv(os.path.join(filtering_path, 'lowread.csv'), index=None)
df = df.drop(index=df_lowread.index)
return df
def sv_filtering_final(df):
svs_lowq = df[(df[fb_gq]<gq_value) | (df[sb_gq]<gq_value)]
svs_lowq.to_csv(os.path.join(filtering_path, 'svs_lowq.csv'), index=None)
df = df.drop(index=svs_lowq.index)
sv_highratio = df[(df[fb_ad_alt]/df[fb_ad_ref]>2) & (df[sb_ad_alt]/df[sb_ad_ref]>2)]
sv_highratio.to_csv(os.path.join(filtering_path, 'highratio.csv'), index=None)
df = df.drop(index=sv_highratio.index)
sv_lowratio = df[(df[fb_ad_alt]/df[fb_ad_ref]<0.5) & (df[sb_ad_alt]/df[sb_ad_ref]<0.5)]
sv_lowratio.to_csv(os.path.join(filtering_path, 'lowratio.csv'), index=None)
df = df.drop(index=sv_lowratio.index)
# SVs with very high LD are likely from the repetitive genetic elements in the genome
# Remove SVs that could be from the repetitive elements
# print([df[fb_ld].mean(), df[fb_ld].std(), df[sb_ld].mean(), df[sb_ld].std()])
df = read_filter(df)
gt_nm = df[df[fb_gt]!=df[sb_gt]]
gt_nm.to_csv(os.path.join(filtering_path, 'gt_nm.csv'), index=None)
# df = df.drop(index=gt_nm.index)
highly_ed = df[((df[fb_ad_alt]/df[fb_ad_ref]>2) & (df[sb_ad_alt]/df[sb_ad_ref]<0.5)) | ((df[fb_ad_alt]/df[fb_ad_ref]<0.5) & (df[sb_ad_alt]/df[sb_ad_ref]>2))]
highly_ed.to_csv(ed_file, index=None)
bulk_homo_svs = sv_highratio = df[((df[fb_ad_alt]==0) & (df[sb_ad_ref]==0)) | ((df[fb_ad_ref]==0) & (df[sb_ad_alt]==0))]
bulk_homo_svs.to_csv(bulk_homo_svs_file, index=None)
return df
def g_statistic_array(o1, o3, o2, o4):
'''
Calculate G-statistic using numpy arrays as input
o1 - o4 are 4 numpy arrays of observed values that are greater than or equal to zero
'''
# Ignore errors caused by 'Divide by zero' or logarithm of zero, and let numpy.where to handle these situations
np.seterr(all='ignore')
# Calculate the expected values under the null hypothesis
e1 = np.where(o1+o2+o3+o4!=0, (o1+o2)*(o1+o3)/(o1+o2+o3+o4), 0)
e2 = np.where(o1+o2+o3+o4!=0, (o1+o2)*(o2+o4)/(o1+o2+o3+o4), 0)
e3 = np.where(o1+o2+o3+o4!=0, (o3+o4)*(o1+o3)/(o1+o2+o3+o4), 0)
e4 = np.where(o1+o2+o3+o4!=0, (o3+o4)*(o2+o4)/(o1+o2+o3+o4), 0)
# Calculate the log-likelihood ratios
llr1 = np.where(o1/e1>0, 2*o1*np.log(o1/e1), 0.0)
llr2 = np.where(o2/e2>0, 2*o2*np.log(o2/e2), 0.0)
llr3 = np.where(o3/e3>0, 2*o3*np.log(o3/e3), 0.0)
llr4 = np.where(o4/e4>0, 2*o4*np.log(o4/e4), 0.0)
return np.where(e1*e2*e3*e4==0, 0.0, llr1+llr2+llr3+llr4)
def sv_ci(row):
'''
Estimate the thresholds of Δ(allele frequency) and G-statistic value using its simulated AD values for each SV in the dataset.
Each row in the dataset represents an SV.
This function will be used only when the module 'Fisher' is available.
'''
# Create an array with 10000 (rep) simulated ALT reads of the SV - first bulk
yb_ld, eb_ld = row[fb_ld], row[sb_ld]
yb_alt_array = np.random.binomial(yb_ld, fb_freq, rep)
# yb_ld_array = np.full(rep, row[fb_ld])
# Create an array with 10000 (rep) simulated ALT reads of the SV - second bulk
eb_alt_array = np.random.binomial(eb_ld, sb_freq, rep)
# eb_ld_array = np.full(rep, row[sb_ld])
# Create simulated allele frequency and Δ(allele frequency) arrays of the SV
# yb_af_array = yb_alt_array/yb_ld
# eb_af_array = eb_alt_array/eb_ld
daf_array = eb_alt_array/eb_ld - yb_alt_array/yb_ld
daf_abs_array = np.abs(daf_array)
# Create a G-statistic array of the SV
gs_array = g_statistic_array(yb_alt_array, yb_ld-yb_alt_array, eb_alt_array, eb_ld-eb_alt_array)
# Obtain the percentile of the above arrays
# ci_yb_af = np.percentile(yb_af_array, percentile_ci)
# ci_eb_af = np.percentile(eb_af_array, percentile_ci)
ci_daf = np.percentile(daf_array, percentile_ci)
ci_gs = np.percentile(gs_array, percentile_th)
ci_daf_abs = np.percentile(daf_abs_array, percentile_th)
# return [ci_yb_af, ci_eb_af, ci_daf, ci_gs]
return [ci_daf, ci_gs, ci_daf_abs]
def sv_ci_scipy(row):
'''
Perform Fisher's exact test for each SV in the dataset using its actual AD values in both bulks
and estimate the thresholds of Δ(allele frequency) and G-statistic value using its simulated AD values.
Each row in the dataset represents an SV.
This function will be used if the 'Fisher' module is not installed. It is slow for large dataset.
'''
# Perform Fisher's exact test for each SV using the actual REF/ALT reads
try:
__, fe = fisher_exact([[row[fb_ad_ref], row[fb_ad_alt]], [row[sb_ad_ref], row[sb_ad_alt]]])
except TypeError:
fe = 'NA'
# Perform Fisher's exact test for each SV using the simulated REF/ALT reads
try:
__, sm_fe = fisher_exact([[row[sm_fb_ad_ref], row[sm_fb_ad_alt]], [row[sm_sb_ad_ref], row[sm_sb_ad_alt]]])
except TypeError:
sm_fe = 'NA'
a = [fe, sm_fe]
if test == True:
a.extend(sv_ci(row))
return a
def sv_ci_gw(df):
# Using this function to calculate the threshold if 'Fisher' is not available
print('Estimate the genome-wide thresholds of the sSV/totalSV, G-statistic, and \u0394(allele frequency) at the SV level.')
ratio_list, gs_list, daf_list, daf_list_abs = [], [], [], []
sv_smpl = df.sample(sv_per_sw, replace=True)
sv_smpl['STAT'] = sv_smpl.apply(sv_ci, axis=1)
# Create new columns containing allele frequency, Δ(allele frequency) confidence intervals, and G-statistic thresholds
# sv_smpl[[fb_af_ci, sb_af_ci, 'DAF_CI', 'GS_CI']] = pd.DataFrame(sv_smpl.STAT.values.tolist(), index=sv_smpl.index)
sv_smpl[['DAF_CI', 'GS_Thrshld', 'DAF_abs_Thrshld']] = pd.DataFrame(sv_smpl.STAT.values.tolist(), index=sv_smpl.index)
sv_smpl['DAF_CI_LB'] = sv_smpl['DAF_CI'].apply(lambda x: x[0]).astype(float)
sv_smpl['DAF_CI_UB'] = sv_smpl['DAF_CI'].apply(lambda x: x[1]).astype(float)
return [sv_smpl['DAF_CI_LB'].mean(), sv_smpl['DAF_CI_UB'].mean(), sv_smpl['GS_Thrshld'].mean(), sv_smpl['DAF_abs_Thrshld'].mean()]
def thresholds_gw_approximate(df):
# Using this function to calculate the threshold if 'Fisher' is not available
print('Estimate the approximate genome-wide thresholds of the sSV/totalSV, G-statistic, and \u0394(allele frequency) at the sliding window level.')
ratio_list, gs_list, daf_list, daf_list_abs = [], [], [], []
for __ in range(rep):
sv_smpl = df.sample(sv_per_sw, replace=True)
ssv_smpl = sv_smpl[sv_smpl['sm_FE_P']<sm_alpha]
ratio_list.append(len(ssv_smpl.index)/sv_per_sw)
gs_list.append(np.mean(g_statistic_array(sv_smpl[fb_ld]-sv_smpl[sm_fb_ad_alt], sv_smpl[sm_fb_ad_alt], sv_smpl[sb_ld]-sv_smpl[sm_sb_ad_alt], sv_smpl[sm_sb_ad_alt])))
daf_list.append(np.mean(sv_smpl[sm_sb_ad_alt]/sv_smpl[sb_ld]-sv_smpl[sm_fb_ad_alt]/sv_smpl[fb_ld]))
daf_list_abs.append(np.mean(np.absolute(sv_smpl[sm_sb_ad_alt]/sv_smpl[sb_ld]-sv_smpl[sm_fb_ad_alt]/sv_smpl[fb_ld])))
misc.append(['Genome-wide sSV/totalSV ratio threshold', np.percentile(ratio_list, percentile_th)])
misc.append(['Genome-wide G-statistic threshold', np.percentile(gs_list, percentile_th)])
misc.append(['Genome-wide Delta(AF) threshold', np.percentile(daf_list, percentile_ci)])
misc.append(['Genome-wide absolute Delta(AF) threshold', np.percentile(daf_list_abs, percentile_th)])
print(f'Threshold calculation completed, time elapsed: {(time.time()-t0)/60} minutes.')
return [np.percentile(ratio_list, percentile_th), np.percentile(gs_list, percentile_th), np.percentile(daf_list, percentile_ci), np.percentile(daf_list_abs, percentile_th)]
def thresholds_gw(df):
# For the calculation of the genome-wide threshold if 'Fisher' is installed
print('Estimate the genome-wide thresholds of the sSV/totalSV, G-statistic, and \u0394(allele frequency).')
gw_ratio_list, gw_gs_list, gw_daf_list, gw_daf_abs_list = [], [], [], []
for __ in range(rep):
sv_smpl = df.sample(sv_per_sw, replace=True)
gw_fb_ad_alt_arr = np.random.binomial(sv_smpl[fb_ld], fb_freq).astype(np.uint32)
gw_fb_ad_ref_arr = sv_smpl[fb_ld].to_numpy().astype(np.uint32) - gw_fb_ad_alt_arr
gw_sb_ad_alt_arr = np.random.binomial(sv_smpl[sb_ld], sb_freq).astype(np.uint32)
gw_sb_ad_ref_arr = sv_smpl[sb_ld].to_numpy().astype(np.uint32) - gw_sb_ad_alt_arr
__, __, gw_sm_fe_p_arr = pvalue_npy(gw_fb_ad_alt_arr, gw_fb_ad_ref_arr, gw_sb_ad_alt_arr, gw_sb_ad_ref_arr)
# gw_sm_fe_OR_arr = (gw_fb_ad_alt_arr * gw_sb_ad_ref_arr)/(gw_fb_ad_ref_arr * gw_sb_ad_alt_arr)
# Calculate sSV/totalSV ratio
sSV_arr = np.where(gw_sm_fe_p_arr<sm_alpha, 1, 0)
gw_ratio_list.append(np.mean(sSV_arr))
# Calculate G-statistic
gw_gs_arr = g_statistic_array(gw_fb_ad_ref_arr, gw_fb_ad_alt_arr, gw_sb_ad_ref_arr, gw_sb_ad_alt_arr)
gw_gs_list.append(np.mean(gw_gs_arr))
# Calculate allele frequency
gw_daf_arr = gw_sb_ad_alt_arr/sv_smpl[sb_ld] - gw_fb_ad_alt_arr/sv_smpl[fb_ld]
gw_daf_list.append(np.mean(gw_daf_arr))
gw_daf_abs_list.append(np.mean(np.absolute(gw_daf_arr)))
misc.append(['Genome-wide sSV/totalSV ratio threshold', np.percentile(gw_ratio_list, percentile_th)])
misc.append(['Genome-wide G-statistic threshold', np.percentile(gw_gs_list, percentile_th)])
misc.append(['Genome-wide Delta(AF) ratio threshold', np.percentile(gw_daf_list, percentile_ci)])
misc.append(['Genome-wide absolute Delta(AF) ratio threshold', np.percentile(gw_daf_abs_list, percentile_th)])
print(f'Threshold calculation completed, time elapsed: {(time.time()-t0)/60} minutes.')
return [np.percentile(gw_ratio_list, percentile_th), np.percentile(gw_gs_list, percentile_th), np.percentile(gw_daf_list, percentile_ci), np.percentile(gw_daf_abs_list, percentile_th)]
def thresholds_sw(df):
# df = df.copy()
# df['DI'] = di(df)
# df = df[df.DI==1]
# For the calculation of the sliding window-specific sSV/totalSV threshold
sw_ratio_list, sw_gs_list, sw_daf_list, sw_daf_abs_list = [], [], [], []
# Convert the LD column to a numpy array
sw_fb_ld_arr = df[fb_ld].to_numpy().astype(np.uint32)
sw_sb_ld_arr = df[sb_ld].to_numpy().astype(np.uint32)
for __ in range(rep):
# Create new columns with simulated AD values based on the LD values
sw_fb_ad_alt_arr = np.random.binomial(df[fb_ld], fb_freq).astype(np.uint32)
sw_fb_ad_ref_arr = sw_fb_ld_arr - sw_fb_ad_alt_arr
sw_sb_ad_alt_arr = np.random.binomial(df[sb_ld], sb_freq).astype(np.uint32)
sw_sb_ad_ref_arr = sw_sb_ld_arr - sw_sb_ad_alt_arr
# Calculate the P-value via Fisher's Exact test
__, __, sw_sm_fe_p_arr = pvalue_npy(sw_fb_ad_alt_arr, sw_fb_ad_ref_arr, sw_sb_ad_alt_arr, sw_sb_ad_ref_arr)
# # Calculate the odd ratio, not needed for BSA-Seq analysis
# sw_sm_fe_OR_arr = (sw_fb_ad_alt_arr * sw_sb_ad_ref_arr)/(sw_fb_ad_ref_arr*sw_sb_ad_alt_arr)
# Calculate sSV/totalSV
sSV_arr = np.where(sw_sm_fe_p_arr<sm_alpha, 1, 0)
sw_ratio_list.append(np.mean(sSV_arr))
# Calculate G-statistic
sw_gs_arr = g_statistic_array(sw_fb_ad_ref_arr, sw_fb_ad_alt_arr, sw_sb_ad_ref_arr, sw_sb_ad_alt_arr)
sw_gs_list.append(np.mean(sw_gs_arr))
# Calculate allele frequency
sw_daf_arr = sw_sb_ad_alt_arr/sw_sb_ld_arr - sw_fb_ad_alt_arr/sw_fb_ld_arr
sw_daf_list.append(np.mean(sw_daf_arr))
sw_daf_abs_list.append(np.mean(np.absolute(sw_daf_arr)))
return [np.percentile(sw_ratio_list, percentile_th), np.percentile(sw_gs_list, percentile_th), np.percentile(sw_daf_list, percentile_ci), np.percentile(sw_daf_abs_list, percentile_th)]
def zeroSV(li):
# Replace 'divide by zero' with the nearest value.
if li != []:
li.append(li[-1]) # Assign the previous value to the empty sliding window if the list is not empty
else:
li.append('empty') # Assign 'empty' to the first sliding windows that is empty
def replace_zero(li):
# Replace the 'empty' placeholders at the beginning of the list with the nearest non-empty value
i = 0
while li[i]=='empty':
i += 1
j = 0
while j < i:
li[j] = li[i]
j += 1
def di(df, frag_size):
# https://stackoverflow.com/questions/20625582/how-to-deal-with-settingwithcopywarning-in-pandas
# https://www.dataquest.io/blog/settingwithcopywarning/
if not os.path.exists(dgns_path):
os.makedirs(dgns_path)
chromosomes = df.CHROM.unique().tolist()
di_list = []
for chrm in chromosomes:
ch = df[df.CHROM==chrm].copy()
first_pos_value = ch['POS'].iloc[0] # ch.at[0, 'POS'] may not work for dataframe subsets
pos_l1 = ch.POS.tolist()
pos_l1.insert(0, first_pos_value-frag_size) # ensure the first SV is included even if it located at the position 1
pos_l1.pop()
ch['POS1'] = pos_l1
ch['DSTNC'] = ch.POS - ch.POS1
l = ch.DSTNC.tolist()
i, a = 0, []
while i < len(l):
if l[i] >= frag_size:
a.append(1)
i += 1
else:
a.append(0)
temp = l[i]
i += 1
m = i
while i < len(l)-1 and temp + l[i] < frag_size:
a.append(0)
temp += l[i]
i += 1
if m != i:
if temp < frag_size and i >= len(l)-1:
a.append(0)
else:
a.append(1)
i += 1
di_list = di_list + a
ch['DI'] = a
temp_df = ch[['POS', 'POS1', 'DSTNC', 'DI']]
if type(chrm) is int:
temp_df.to_csv(os.path.join(dgns_path, str(chrm)+'_'+str(first_pos_value)+'.csv'), index=None)
else:
temp_df.to_csv(os.path.join(dgns_path, chrm+'_'+str(first_pos_value)+'.csv'), index=None)
return di_list
def bsaseq_plot(df):
'''
wm_list: list of warning messages
sw_dict: a dictionary with the chromosome ID as its keys; the value of each key is a list containing the chromosome ID, the sSV/totalSV ratio in each sliding window, and the startpoint of the sliding window
misc: miscellaneous information
sv_region: genomic region above the threshold
sw_data_frame: DataFrame containing sliding windows
'''
print('Prepare SV data for plotting via the sliding window algorithm')
tl = [i for i in selected_chrms]
for x in tl:
y = df[df.CHROM==x]
# print(x, len(y.index))
if len(y.index) < min_sv:
selected_chrms.remove(x)
# print(selected_chrms)
if selected_chrms == []:
print('Your dataset contains too few SVs. No analysis is performed')
sys.exit()
# Plot layout setup
height_ratio = [1,0.8,0.8,0.8]
xt_pro = xticks_property(chrmSzL)
fig, axs = plt.subplots(nrows=len(height_ratio), ncols=len(selected_chrms), figsize=(20, 12.8), sharex='col', sharey='row', gridspec_kw={'width_ratios': chrmSzL, 'height_ratios': height_ratio})
global misc
global sv_region, sw_data_frame
sg_y_ratio_list = [] # Smoothed sSV/totalSV ratios of a chromosome or a selected genomic region
sg_gs_list = []
sg_t_gs_list = []
sg_daf_list = []
sg_nt_daf_list = []
sg_pt_daf_list = []
sw_rows = []
wm_list, sw_dict, sv_region = [], {}, []
hdr = df.columns.values.tolist()
# Analyze each chromosome separately
num_sv_on_chr = [] # List containing the sSV/totalSV, # of sSVs, and # of totalSVs of a chromosome
ratio_peak_list = [] # List containing the peak of each chromosome
i = 1
for chrm in selected_chrms:
plot_sp = chrmSzD[i-1][0] # The start point of the selected genomic region in the plot
plot_ep = chrmSzD[i-1][1]
ch = df[(df.CHROM==chrm) & (df.POS>=plot_sp) & (df.POS<=plot_ep)]
num_sv_on_chr.append([chrm, ch['sSV'].sum(), len(ch.index), ch['sSV'].sum()/len(ch.index)])
sw_str = plot_sp # The beginning of the window
sw_end = plot_sp+sw_size # The end of the window
icrs = incremental_step # The incremental step
x = [] # Sliding window position on the chromosome
y = [] # Number of sSVs in the sliding window
y_total = [] # Number of total SVs in the sliding window
y_ratio = [] # sSV/totalSV ratio of the sliding window
y5 = [] # G-statistic
y6 = [] # The threshold of the G-statistic
y7 = [] # y7: Δ(allele frequency)
y8, y9 = [], [] # The confidence interval of the Δ(allele frequency)
# chrm_size = plot_ep - plot_sp
# adj_ratio = (chrm_size - sw_size) / chrm_size
# adj_POS = plot_sp + (ch.POS - plot_sp) * adj_ratio
# Calculate, sSV/totalSV, G-statistic, and Δ(allele frequency) of a sliding window
while sw_end <= plot_ep:
# sw_df: sSVs in a sliding window; sw_dfT: all SVs in a sliding window
sw_df = ch[(ch.POS>=sw_str) & (ch.POS<=sw_end)]
# a = sw_df[fb_ld].mean()
# b = sw_df[sb_ld].mean()
# c = sw_df['sSV'].sum()
# d = sw_df['sSV'].mean()
# e = sw_df['G_S'].sum()
# f = sw_df['Delta_AF'].abs().sum()
# g = sw_df['Delta_AF'].sum()
row_in_sw_df = len(sw_df.index) # number of SVs in a sliding window
x.append(sw_str) # Append the start point of the sliding window to x
y.append(sw_df['sSV'].sum().item()) # Append number of sSVs of the sliding window to y
y_total.append(row_in_sw_df) # Append number of totalSVs of the sliding window to y_total
# 'try/exception' cannot catch the EmptyDataError; seems it only works when reading a .csv/.tsv file, not an empty subset of a existing dataframe. DivisionByZero generates 'nan' for a series or an array.
if row_in_sw_df >= min_SVs:
y_ratio.append(sw_df['sSV'].mean().item()) # Append the sSV/totalSV ratio of the sliding window to y_ratio
y5.append(sw_df['G_S'].sum().item()/row_in_sw_df)
if num_ipfiles == 1 and parent1 != 'ref':
y7.append(sw_df['Delta_AF'].abs().sum().item()/row_in_sw_df)
else:
y7.append(sw_df['Delta_AF'].sum().item()/row_in_sw_df)
if test == True and 'GS_Thrshld' in hdr:
y6.append(sw_df['GS_Thrshld'].sum().item()/row_in_sw_df)
y8.append(sw_df['DAF_CI_LB'].sum().item()/row_in_sw_df)
y9.append(sw_df['DAF_CI_UB'].sum().item()/row_in_sw_df)
# useful info of the sliding window
row_contents = [chrm, sw_str, sw_df[fb_ld].mean().item(), sw_df[sb_ld].mean().item(), sw_df['sSV'].sum().item(), row_in_sw_df, y_ratio[-1], y5[-1], y6[-1], y7[-1], y8[-1], y9[-1]]
else:
row_contents = [chrm, sw_str, sw_df[fb_ld].mean().item(), sw_df[sb_ld].mean().item(), sw_df['sSV'].sum().item(), row_in_sw_df, y_ratio[-1], y5[-1], y7[-1]]
else:
wm_list.append(['No SV in the sliding window', i, sw_str])
zeroSV(y_ratio)
zeroSV(y5)
zeroSV(y7)
# The mean of an empty column is 'nan', not zero, and int(nan) generates a ValueError
if test == True and 'GS_Thrshld' in hdr:
zeroSV(y6)
zeroSV(y8)
zeroSV(y9)
row_contents = [chrm, sw_str, 0, 0, sw_df['sSV'].sum().item(), row_in_sw_df, y_ratio[-1], y5[-1], y6[-1], y7[-1], y8[-1], y9[-1]]
else:
row_contents = [chrm, sw_str, 0, 0, sw_df['sSV'].sum().item(), row_in_sw_df, y_ratio[-1], y5[-1], y7[-1]]
sw_rows.append(row_contents)
if i not in sw_dict:
sw_dict[i] = [row_contents]
else:
sw_dict[i].append(row_contents)
sw_str += icrs
sw_end += icrs
# Replace the 'empty' values at the beginning of the lists with nearest non-empty value
for yl in [y_ratio, y5, y6, y7, y8, y9]:
if 'empty' in yl:
replace_zero(yl)
# Find the first non-empty value in the sw_dict
pIndex = 0
while sw_dict[i][pIndex][6] == 'empty':
pIndex += 1
# Replace the empty value(s) with the above non-empty value in the sw_dict
j = 0
while j < pIndex:
sw_dict[i][j][6] = sw_dict[i][pIndex][6]
sw_dict[i][j][7] = sw_dict[i][pIndex][7]
sw_dict[i][j][8] = sw_dict[i][pIndex][8]
if test == True:
sw_dict[i][j][9] = sw_dict[i][pIndex][9]
sw_dict[i][j][10] = sw_dict[i][pIndex][10]
sw_dict[i][j][11] = sw_dict[i][pIndex][11]
j += 1
# Smoothing data of a chromosome or a selected region
sg_y_ratio = savgol_filter(y_ratio, smth_wl, poly_order)
sg_y5 = savgol_filter(y5, smth_wl, poly_order)
sg_y7 = savgol_filter(y7, smth_wl, poly_order)
sg_y_ratio_list.extend(sg_y_ratio)
sg_gs_list.extend(sg_y5)
sg_daf_list.extend(sg_y7)
if test == True and 'GS_Thrshld' in hdr:
sg_y6 = savgol_filter(y6, smth_wl, poly_order)
sg_y8 = savgol_filter(y8, smth_wl, poly_order)
sg_y9 = savgol_filter(y9, smth_wl, poly_order)
sg_t_gs_list.extend(sg_y6)
sg_nt_daf_list.extend(sg_y8)
sg_pt_daf_list.extend(sg_y9)
# Handle the plot with a single column (chromosome)
if len(selected_chrms) == 1:
# Set up x-ticks
axs[0].set_xticks(np.arange(plot_sp, x[-1], xt_pro[0]))
ticks = axs[0].get_xticks()*xt_pro[1]
axs[0].set_xticklabels(ticks.astype(int))
# Add ylabels to the first column of the subplots
if i==1:
axs[0].set_ylabel('Number of SVs')
axs[1].set_ylabel(r'sSV/totalSV')
axs[2].set_ylabel('$G$-statistic')
axs[3].set_ylabel('\u0394$AF$')
# Plot sSVs and total SVs against their genomic positions
axs[0].plot(x, y, c=curve_color)
axs[0].plot(x, y_total, c=ttl_sv_color)
if chrm.isdigit() == True:
axs[0].set_title('Chr'+chrm)
elif '.' in chrm:
axs[0].set_title('Chr'+chrm_prnt_dict[chrm])
else:
axs[0].set_title(chrm)
# Plot sSV/totalSV, G-statistic, and Δ(allele frequency) against their genomic positions
if smoothing == True:
# sSVs/totalSVs via Fisher's exact test
axs[1].plot(x, sg_y_ratio, c=curve_color)
# axs[1].scatter(adj_POS, ch.FE_P, marker=',', s=0.4, c=bg_color, zorder=-1)
# axs[1].scatter(adj_POS, 1-ch.FE_P, marker=',', s=0.4, c=bg_color, zorder=-1)
# G-statistic
axs[2].plot(x, sg_y5, c=curve_color)
# axs[2].scatter(adj_POS, ch.G_S, marker=',', s=0.4, c=bg_color, zorder=-1)
# Δ(allele frequency)
axs[3].plot(x, sg_y7, c=curve_color)
#axs[3].scatter(adj_POS, ch.Delta_AF, marker=',', s=0.4, c=bg_color, zorder=-1)
if test == True and 'GS_Thrshld' in hdr:
# G-statistic threshold at the SV level
axs[2].plot(x, sg_y6, c=sv_threshold_color)
# Δ(allele frequency) confidence interval at the SV level
if num_ipfiles == 1 and parent1 != 'ref':
axs[3].plot(x, sg_y9, c=sv_threshold_color)
elif num_ipfiles == 2 or parent1 == 'ref':
axs[3].plot(x, sg_y8, c=sv_threshold_color)
axs[3].plot(x, sg_y9, c=sv_threshold_color)
else:
# sSVs/totalSVs via Fisher's exact test
axs[1].plot(x, y_ratio, c=curve_color)
# axs[1].scatter(adj_POS, ch.FE_P, marker=',', s=0.4, c=bg_color, zorder=-1)
# axs[1].scatter(adj_POS, 1-ch.FE_P, marker=',', s=0.4, c=bg_color, zorder=-1)
# G-statistic
axs[2].plot(x, y5, c=curve_color)
# axs[2].scatter(adj_POS, ch.G_S, marker=',', s=0.4, c=bg_color, zorder=-1)
# Δ(allele frequency)
axs[3].plot(x, y7, c=curve_color)
# axs[3].scatter(adj_POS, ch.Delta_AF, marker=',', s=0.4, c=bg_color, zorder=-1)
if test == True and 'GS_Thrshld' in hdr:
# G-statistic threshold at the SV level
axs[2].plot(x, y6, c=sv_threshold_color)
# Δ(allele frequency) confidence interval at the SV level
if num_ipfiles == 1 and parent1 != 'ref':
axs[3].plot(x, sg_y9, c=sv_threshold_color)
elif num_ipfiles == 2 or parent1 == 'ref':
axs[3].plot(x, sg_y8, c=sv_threshold_color)
axs[3].plot(x, sg_y9, c=sv_threshold_color)
# Add the 99.5 percentile line as the threshold, x[-1] is the startpoint of the last sliding window of a chromosome
axs[1].plot([plot_sp, x[-1]], [thrshld_fe, thrshld_fe], c=sw_threshold_color)
# axs[2].plot([plot_sp, x[-1]], [thrshld_gs, thrshld_gs], c=sw_threshold_color)
axs[2].plot([plot_sp, x[-1]], [sv_ci_gs, sv_ci_gs], c=sw_threshold_color)
# axs[3].plot([plot_sp, x[-1]], [thrshld_af, thrshld_af], c=sw_threshold_color)
# axs[3].plot([plot_sp, x[-1]], [thrshld_af*(-1), thrshld_af*(-1)], c=sw_threshold_color)
if num_ipfiles == 1 and parent1 != 'ref':
# axs[3].plot([plot_sp, x[-1]], [thrshld_af_abs, thrshld_af_abs], c=sw_threshold_color)
axs[3].plot([plot_sp, x[-1]], [sv_ci_af_abs, sv_ci_af_abs], c=sw_threshold_color)
elif num_ipfiles == 2 or parent1 == 'ref':
# axs[3].plot([plot_sp, x[-1]], [thrshld_af_n, thrshld_af_n], c=sw_threshold_color)
# axs[3].plot([plot_sp, x[-1]], [thrshld_af_p, thrshld_af_p], c=sw_threshold_color)
axs[3].plot([plot_sp, x[-1]], [sv_ci_af_lb, sv_ci_af_lb], c=sw_threshold_color)
axs[3].plot([plot_sp, x[-1]], [sv_ci_af_ub, sv_ci_af_ub], c=sw_threshold_color)
# axs[1].plot(x, sm_thresholds_sw, c='m')
# Handle the plot with multiple columns (chromosomes)
else:
# Set up x-ticks
axs[0,i-1].set_xticks(np.arange(plot_sp, x[-1], xt_pro[0]))
ticks = axs[0,i-1].get_xticks()*xt_pro[1]
axs[0,i-1].set_xticklabels(ticks.astype(int))
# Add ylabels to the first column of the subplots
if i==1:
axs[0,i-1].set_ylabel('Number of SVs')
axs[1,i-1].set_ylabel(r'sSV/totalSV')
axs[2,i-1].set_ylabel('$G$-statistic')
axs[3,i-1].set_ylabel('\u0394$AF$')
# Plot sSV and totalSVs against their genomic positions
axs[0,i-1].plot(x, y, c=curve_color)
axs[0,i-1].plot(x, y_total, c=ttl_sv_color)
if chrm.isdigit() == True:
axs[0,i-1].set_title('Chr'+chrm)
elif '.' in chrm:
axs[0,i-1].set_title('Chr'+chrm_prnt_dict[chrm])
else:
axs[0,i-1].set_title(chrm)
# Plot sSV/totalSV, G-statistic, and Δ(allele frequency) against their genomic positions
if smoothing == True:
# sSVs/totalSVs via Fisher's exact test
axs[1,i-1].plot(x, sg_y_ratio, c=curve_color)
# axs[1,i-1].scatter(adj_POS, ch.FE_P, marker=',', s=0.4, c=bg_color, zorder=-1)
# axs[1,i-1].scatter(adj_POS, 1-ch.FE_P, marker=',', s=0.4, c=bg_color, zorder=-1)
# G-statistic
axs[2,i-1].plot(x, sg_y5, c=curve_color)
# axs[2,i-1].scatter(adj_POS, ch.G_S, marker=',', s=0.4, c=bg_color, zorder=-1)
# Δ(allele frequency)
axs[3,i-1].plot(x, sg_y7, c=curve_color)
# axs[3,i-1].scatter(adj_POS, ch.Delta_AF, marker=',', s=0.4, c=bg_color, zorder=-1)
if test == True and 'GS_Thrshld' in hdr:
# G-statistic threshold at the SV level
axs[2,i-1].plot(x, sg_y6, c=sv_threshold_color)
# Δ(allele frequency) confidence interval at the SV level
if num_ipfiles == 1 and parent1 != 'ref':
axs[3,i-1].plot(x, sg_y9, c=sv_threshold_color)
elif num_ipfiles == 2 or parent1 == 'ref':
axs[3, i-1].plot(x, sg_y8, c=sv_threshold_color)
axs[3, i-1].plot(x, sg_y9, c=sv_threshold_color)
else:
# sSVs/totalSVs via Fisher's exact test
axs[1,i-1].plot(x, y_ratio, c=curve_color)
# axs[1,i-1].scatter(adj_POS, ch.FE_P, s=0.4, marker=',', c=bg_color, zorder=-1)
# axs[1,i-1].scatter(adj_POS, 1-ch.FE_P, s=0.4, marker=',', c=bg_color, zorder=-1)
# G-statistic
axs[2,i-1].plot(x, y5, c=curve_color)
# axs[2,i-1].scatter(adj_POS, ch.G_S, marker=',', s=0.4, c=bg_color, zorder=-1)
# Δ(allele frequency)
axs[3,i-1].plot(x, y7, c=curve_color)
# axs[3,i-1].scatter(adj_POS, ch.Delta_AF, marker=',', s=0.4, c=bg_color, zorder=-1)
if test == True and 'GS_Thrshld' in hdr:
# G-statistic threshold at the SV level
axs[2,i-1].plot(x, y6, c=sv_threshold_color)
# Δ(allele frequency) confidence interval at the SV level
if num_ipfiles == 1 and parent1 != 'ref':
axs[3,i-1].plot(x, sg_y9, c=sv_threshold_color)