Skip to content

Commit 9b47abe

Browse files
committed
Move features in JSON file to config YAML
1 parent 7b8218b commit 9b47abe

8 files changed

Lines changed: 92 additions & 76 deletions

File tree

config/config.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ ALIGNMENT_REFERENCE:
22
"NC_045512.2"
33
PROBLEMATIC_VCF:
44
"https://raw.githubusercontent.com/W-L/ProblematicSites_SARS-CoV2/da322c32004f7b16bdaa6a8ee7fd24d56e79d9dc/problematic_sites_sarsCov2.vcf"
5-
FEATURES_JSON:
6-
"config/sarscov2_features.json"
75
COORDINATES_JSON:
86
"config/sarscov2_coordinates.json"
97
GENETIC_CODE_JSON:
@@ -65,6 +63,19 @@ DEMIX:
6563
WINDOW:
6664
WIDTH: 1000
6765
STEP: 50
66+
GB_FEATURES:
67+
"ORF1ab polyprotein": "ORF1ab"
68+
"ORF1a polyprotein": "ORF1ab"
69+
"surface glycoprotein": "S"
70+
"ORF3a protein": "ORF3a"
71+
"envelope protein": "E"
72+
"membrane glycoprotein": "M"
73+
"ORF6 protein": "ORF6"
74+
"ORF7a protein": "ORF7"
75+
"ORF7b": "ORF7"
76+
"ORF8 protein": "ORF8"
77+
"nucleocapsid phosphoprotein": "N"
78+
"ORF10 protein": "ORF10"
6879
GISAID:
6980
CREDENTIALS: "config/gisaid.yaml"
7081
DATE_COLUMN: "CollectionDate"

config/sarscov2_features.json

Lines changed: 0 additions & 14 deletions
This file was deleted.

workflow/rules/evolution.smk

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
rule N_S_sites:
22
threads: 1
33
conda: "../envs/biopython.yaml"
4+
params:
5+
gb_features = config["GB_FEATURES"],
46
input:
57
fasta = OUTDIR/f"{OUTPUT_NAME}.ancestor.fasta",
68
gb = OUTDIR/"reference.gb",
7-
features = Path(config["FEATURES_JSON"]).resolve(),
8-
genetic_code = Path(config["GENETIC_CODE_JSON"]).resolve()
9+
genetic_code = Path(config["GENETIC_CODE_JSON"]).resolve(),
910
output:
10-
csv = temp(OUTDIR/f"{OUTPUT_NAME}.ancestor.N_S.sites.csv")
11+
csv = temp(OUTDIR/f"{OUTPUT_NAME}.ancestor.N_S.sites.csv"),
1112
log:
1213
LOGDIR / "N_S_sites" / "log.txt"
1314
script:

workflow/rules/report.smk

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ rule window:
4444
conda: "../envs/biopython.yaml"
4545
params:
4646
window = config["WINDOW"]["WIDTH"],
47-
step = config["WINDOW"]["STEP"]
47+
step = config["WINDOW"]["STEP"],
48+
select_gb_features = config.get("GB_FEATURES", {}), # if empty, uses all available features
4849
input:
4950
vcf = OUTDIR/f"{OUTPUT_NAME}.variants.tsv",
5051
gb = OUTDIR/"reference.gb",
51-
features = config["FEATURES_JSON"]
5252
output:
5353
window_df = temp(OUTDIR/f"{OUTPUT_NAME}.window.csv"),
5454
log:
@@ -229,7 +229,7 @@ rule evo_plots:
229229
params:
230230
design = config["PLOTS"]
231231
input:
232-
N_S = OUTDIR/f"{OUTPUT_NAME}.ancestor.N_S.sites.csv",
232+
n_s_sites = OUTDIR/f"{OUTPUT_NAME}.ancestor.N_S.sites.csv",
233233
vcf = OUTDIR/f"{OUTPUT_NAME}.variants.tsv",
234234
metadata = config["METADATA"]
235235
output:

workflow/scripts/N_S_sites_from_fasta.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
def split_into_codons(seq: str) -> list:
1313
"""Split the complete CDS feature in to a list of codons"""
14-
codons = [seq[i:i + 3] for i in range(0, len(seq), 3) if "N" not in seq[i:i + 3]]
14+
codons = [
15+
seq[i:i + 3] for i in range(0, len(seq), 3) if "N" not in seq[i:i + 3]
16+
]
1517
return codons
1618

1719

@@ -29,7 +31,7 @@ def calculate_potential_changes(genetic_code: dict) -> dict:
2931
for codon_p in range(0, 3):
3032
nts = ["A", "G", "T", "C"]
3133
# Do not consider self substitutions, e.g. A->A
32-
nts.remove(codon[codon_p])
34+
nts.remove(codon[codon_p])
3335
for nt in nts:
3436
codon_mutated = list(codon)
3537
# Mutate the basepair
@@ -43,49 +45,55 @@ def calculate_potential_changes(genetic_code: dict) -> dict:
4345

4446

4547
def get_feature_codons(alignment: Gb2Alignment, annotation: list) -> dict:
46-
dct = {key:alignment.ntSequences(key)[1].sequence for key in annotation}
47-
return {key:split_into_codons(item) for key,item in dct.items()}
48+
dct = {key: alignment.ntSequences(key)[1].sequence for key in annotation}
49+
return {key: split_into_codons(item) for key, item in dct.items()}
4850

4951

50-
def get_df(codons: dict, genetic_code: dict) -> pd.DataFrame:
51-
keys = []
52+
def calculate_ns_sites(codons: dict, genetic_code: dict) -> pd.DataFrame:
53+
features = []
5254
N_sites = []
5355
S_sites = []
5456
values = calculate_potential_changes(genetic_code)
5557
for key, item in codons.items():
56-
keys.append(key)
58+
features.append(key)
5759
N = sum([values["N"][x] for x in item if x in values["N"].keys()])
5860
S = sum([values["S"][x] for x in item if x in values["S"].keys()])
5961
N_sites.append(N)
60-
S_sites.append(S)
61-
return pd.DataFrame({"gene": keys, "N": N_sites, "S": S_sites})
62+
S_sites.append(S)
63+
return pd.DataFrame({"gene": features, "N": N_sites, "S": S_sites})
6264

6365

6466
def main():
6567

66-
logging.basicConfig(filename=snakemake.log[0], format=snakemake.config["LOG_PY_FMT"], level=logging.INFO)
67-
68+
logging.basicConfig(
69+
filename=snakemake.log[0], format=snakemake.config["LOG_PY_FMT"],
70+
level=logging.INFO
71+
)
72+
6873
logging.info("Reading features")
69-
with open(snakemake.input.features) as f:
70-
feature_list = list(json.load(f).keys())
74+
feature_list = list(snakemake.params.gb_features.keys())
7175

7276
logging.info("Reading genetic code")
7377
with open(snakemake.input.genetic_code) as f:
7478
genetic_code = json.load(f)
75-
79+
7680
logging.info("Create alignment object")
7781
features = Features(snakemake.input.gb)
78-
seq = list(FastaReads(snakemake.input.fasta))[0]
82+
fasta_reads = list(FastaReads(snakemake.input.fasta))
83+
if len(fasta_reads) > 1:
84+
logging.warning(
85+
f"More than one record found in {snakemake.input.fasta}, selecting the first one")
86+
seq = fasta_reads[0]
7987
aln = Gb2Alignment(seq, features)
8088

81-
logging.info("Splitting ancestral sequence into codons")
82-
codons_dict = get_feature_codons(aln, feature_list)
89+
logging.info("Splitting input sequence into codons")
90+
codons = get_feature_codons(aln, feature_list)
8391

8492
logging.info("Calculating synonymous and non synonymous sites")
85-
df = get_df(codons_dict, genetic_code)
93+
df = calculate_ns_sites(codons, genetic_code)
8694

8795
logging.info("Saving results")
88-
df.to_csv(snakemake.output.csv,index= False)
96+
df.to_csv(snakemake.output.csv, index=False)
8997

9098

9199
if __name__ == "__main__":

workflow/scripts/report/NV_description.R

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ window_plot <- window %>%
198198
ggplot() +
199199
aes(
200200
x = position,
201-
y = fractions,
202-
color = gen
201+
y = fraction,
202+
color = feature
203203
) +
204204
geom_point() +
205205
geom_line(
@@ -209,7 +209,7 @@ window_plot <- window %>%
209209
) +
210210
scale_y_continuous(
211211
label = scales::percent,
212-
limits = c(0, max(window$fractions) + 0.005)
212+
limits = c(0, max(window$fraction) + 0.005)
213213
) +
214214
xlim(c(0, 29903)) +
215215
scale_color_manual(values = GENE_PALETTE) +
@@ -235,7 +235,7 @@ window_plot_nsp <- window_plot +
235235
data = npc,
236236
aes(
237237
x = (summary_start + summary_end) / 2,
238-
y = max(window$fractions) + 0.002,
238+
y = max(window$fraction) + 0.002,
239239
label = summary_nsp
240240
),
241241
inherit.aes = FALSE,
@@ -266,9 +266,8 @@ ggsave(
266266

267267
# Zoom in in spike
268268
log_info("Plotting summary for variants in the spike")
269-
270269
spike_pos <- window %>%
271-
filter(gen == "S") %>%
270+
filter(feature == "S") %>%
272271
pull(position)
273272

274273
vcf_spike <- vcf %>%
@@ -278,12 +277,12 @@ vcf_spike <- vcf %>%
278277
)
279278

280279
window_plot_spike <- window %>%
281-
filter(gen == "S") %>%
280+
filter(feature == "S") %>%
282281
ggplot() +
283282
aes(
284283
x = position,
285-
y = fractions,
286-
color = gen
284+
y = fraction,
285+
color = feature
287286
) +
288287
geom_point() +
289288
geom_line(
@@ -293,7 +292,7 @@ window_plot_spike <- window %>%
293292
) +
294293
scale_y_continuous(
295294
label = scales::percent,
296-
limits = c(0, max(window$fractions) + 0.005)
295+
limits = c(0, max(window$fraction) + 0.005)
297296
) +
298297
xlim(c(min(spike_pos), max(spike_pos))) +
299298
scale_color_manual(
@@ -438,8 +437,8 @@ vcf %>%
438437
window %>%
439438
transmute(
440439
POS = position,
441-
feature = gen,
442-
prop_PolymorphicSites = fractions
440+
feature = feature,
441+
prop_PolymorphicSites = fraction
443442
) %>%
444443
write.csv(snakemake@output[["table_1"]], row.names = FALSE)
445444

workflow/scripts/report/evo_plots.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ source(snakemake@params[["design"]])
1515
# Read inputs
1616
vcf <- read_delim(snakemake@input[["vcf"]])
1717
metadata <- read_delim(snakemake@input[["metadata"]])
18-
N_S_position <- read_delim(snakemake@input[["N_S"]])
18+
N_S_position <- read_delim(snakemake@input[["n_s_sites"]])
1919

2020
# DATA PROCESSING
2121
# Create SNP variable and select useful variables

workflow/scripts/window.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,57 @@
66
from gb2seq.features import Features
77

88

9-
def window_calculation(sites: set, window: int, step: int, coord: str) -> pd.DataFrame:
10-
ft = Features(coord) # Create Features object to obtain annotations
11-
positions = []
12-
pct = []
13-
genes = []
14-
lim_sup = len(ft.reference.sequence) + 1
9+
def window_calculation(sites: set, window: int, step: int, gb_features: Features) -> pd.DataFrame:
10+
positions, fractions, features = [], [], []
11+
lim_sup = len(gb_features.reference.sequence) + 1
1512
for position in range(1, lim_sup, step):
16-
if len(ft.getFeatureNames(position)) == 0:
17-
genes.append("Intergenic")
13+
if len(gb_features.getFeatureNames(position)) == 0:
14+
features.append("Intergenic")
1815
else:
19-
genes.append(list(ft.getFeatureNames(position))[0])
16+
# If more than one feature on a site, include both (sorted lexicographically)
17+
features.append("|".join(sorted(gb_features.getFeatureNames(position))))
2018
# Add percent (excluding initial and final positions)
2119
if position - window not in range(1, lim_sup):
22-
pct.append(0)
20+
fractions.append(0.0)
2321
else:
2422
# Calculate no. of polimorphisms in the window
25-
num_snp = len([x for x in sites if x in range(position - window, position + 1)])
26-
pct.append(num_snp / window)
23+
num_snp = len([x for x in sites if x in range(
24+
position - window, position + 1)])
25+
fractions.append(num_snp / window)
2726
positions.append(position)
28-
return pd.DataFrame({"position": positions, "fractions": pct, "gen": genes})
27+
return pd.DataFrame({"position": positions, "fraction": fractions, "feature": features})
2928

3029

3130
def main():
3231

33-
logging.basicConfig(filename=snakemake.log[0], format=snakemake.config["LOG_PY_FMT"], level=logging.INFO)
32+
logging.basicConfig(
33+
filename=snakemake.log[0], format=snakemake.config["LOG_PY_FMT"],
34+
level=logging.INFO
35+
)
3436

3537
logging.info("Reading input VCF")
3638
df = pd.read_table(snakemake.input.vcf)
3739
sites = set(df.POS)
3840

39-
logging.info("Reading features")
40-
with open(snakemake.input.features) as f:
41-
features_key = json.load(f)
41+
logging.info("Reading genbank features")
42+
features = Features(snakemake.input.gb)
43+
44+
logging.info("Calculating polimorphic sites sliding window")
45+
windows = window_calculation(
46+
sites,
47+
snakemake.params.window,
48+
snakemake.params.step,
49+
features
50+
)
51+
52+
if len(snakemake.params.select_gb_features) != 0:
53+
logging.info("Filtering and renaming genbank features")
54+
windows = windows[
55+
windows.feature.isin(snakemake.params.select_gb_features.keys())
56+
].replace(snakemake.params.select_gb_features)
4257

43-
logging.info("Sliding window calculation of proportion of polimorphic sites")
44-
frame = window_calculation(sites, snakemake.params.window, snakemake.params.step, snakemake.input.gb)
45-
4658
logging.info("Saving results")
47-
frame.replace(features_key, inplace = True)
48-
frame.to_csv(snakemake.output.window_df, index= False)
59+
windows.to_csv(snakemake.output.window_df, index=False)
4960

5061

5162
if __name__ == "__main__":

0 commit comments

Comments
 (0)