-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathextract_distributions.awk
More file actions
executable file
·276 lines (227 loc) · 9.09 KB
/
Copy pathextract_distributions.awk
File metadata and controls
executable file
·276 lines (227 loc) · 9.09 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
#!/usr/bin/env -S gawk -f
#
# SPDX-FileCopyrightText: 2024, 2025 Marcel Schilling <mschilling@ub.edu>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
# RNA-seq data statistic extraction Awk script for `scr4eam` scRNA-seq
# read simulation pipeline.
#
# Copyright (C) 2024, 2025 Marcel Schilling
#
# This file is part of `scr4eam`.
#
# `scr4eam` is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#######################
# General information #
#######################
# File: extract_distributions.awk
# Created: 2024-12-12
# Modified: 2025-07-11
# Author: Marcel Schilling <@mschilling@ub.edu>
# License: GNU Affero General Public License Version >= 3.0 (GNU AGPL v3+)
# Purpose: Extract distributions required for read generation using `scr4eam`
# from read and fragment mapping summary TSV generated by SCALPEL.
#####################################
# Changelog (reverse chronological) #
#####################################
# 2025-07-11: Email: IDIBELL -> UB.
# 2024-12-12: Initial working version.
#########
# Usage #
#########
# ```sh
# [gawk --file=]extract_distributions.awk \
# [--assign=multi_isoform_genes_txt=<multi-isoform-genes-txt>] \
# [--assign=read_dists_anchors_skipped_txt=<read-dists-anchors-skipped-txt>] \
# [--assign=n_reads_per_fragment_txt=<n-reads-per-fragment-txt>] \
# [--assign=read_distance_col=<read-distance-column>] \
# [--assign=gene_col=<gene-column>] \
# [--assign=isoform_col=<isoform-column>] \
# [--assign=fragment_col=<fragment-column>] \
# [--assign=read_col=<read-column>] \
# [--assign=fragment_distance_col=<fragment-distance-column>] \
# [< <fragments-and-reads-stats-tsv>] \
# [> <unambiguous-fragment-dists-per-gene-tsv>]
# ```
#
# Command line arguments:
# * `<fragments-and-reads-stats-tsv>`: TSV file/stream to read read and
# fragment mapping summary data (e.g. as
# generated by [SCALPEL](scalpel)) from;
# default: `STDIN`
# * `<unambiguous-fragment-dists-per-gene-tsv>`: TSV file/stream to write the
# assigned gene and the
# fragment's distance (0-based)
# to the transcript's (3') end
# for each unambiguously
# assigned fragment to; default:
# `STDOUT`
# * `multi_isoform_genes_txt`: Text file to write genes expressing more than
# just a single isoform to (one gene per line);
# default: `genes.multiple_expressed_isoforms.txt`
# * `read_dists_anchors_skipped_txt`: Text file to write distances (0-based) to
# the fragment's 3' end for all non-anchor
# reads (i.e. excluding the first
# zero-distance read of each fragment) to
# (one integer per line); default:
# `read_dists.anchors_skipped.txt`
# * `n_reads_per_fragment_txt`: File name/path to write total read counts
# for each fragment to; default:
# `n_reads.per_fragment.txt`
# * `read_distance_col`: Column index (1-based) in the input TSV containing
# the read's distance (0-based) from the
# transcript's (3') end; default: `9`
# * `gene_col`: Column index (1-based) in the input TSV specifying the gene
# the read was assigned to; default: `11`
# * `isoform_col`: Column index (1-based) in the input TSV specifying the
# isoform the read was assigned to; default: `12`
# * `fragment_col`: Column index (1-based) in the input TSV specifying the
# fragment the read was assigned to; default: `13`
# * `read_col`: Column index (1-based) in the input TSV identifying the
# read the input line belongs to; default: `14`
# * `fragment_distance_col`: Column index (1-based) in the input TSV
# containing the fragment's distance (0-based)
# from the transcript's (3') end; default: `15`
##############
# Parameters #
##############
BEGIN {
# Default output file names:
if(!multi_isoform_genes_txt) {
multi_isoform_genes_txt = "genes.multiple_expressed_isoforms.txt"
}
if(!read_dists_anchors_skipped_txt) {
read_dists_anchors_skipped_txt = "read_dists.anchors_skipped.txt"
}
if(!n_reads_per_fragment_txt) {
n_reads_per_fragment_txt = "n_reads.per_fragment.txt"
}
# Default input column indices:
if(!read_distance_col) { read_distance_col = 9 }
if(!gene_col) { gene_col = 11 }
if(!isoform_col) { isoform_col = 12 }
if(!fragment_col) { fragment_col = 13 }
if(!read_col) { read_col = 14 }
if(!fragment_distance_col) { fragment_distance_col = 15 }
#####################
# In-/Output format #
#####################
# Read & write TSV.
OFS = FS = "\t"
#############
# Constants #
#############
# Boolean helpers for readabilty:
false = 0
true = !false
}
##########################
# Input parsing (i): IDs #
##########################
# Parse isoform & fragment/read IDs.
{
next_isoform = $isoform_col
next_fragment = $fragment_col
next_read = $read_col
}
##################
# Initialization #
##################
# Initialise first read/fragment.
NR == 1 {
fragment = next_fragment
fragment_isoform = isoform = next_isoform
read = next_read
read_distance = $read_distance_col
fragment_distance = $fragment_distance_col
}
############################
# Read/fragment processing #
############################
# Process reads after gathering all related data.
# Note: The same read can follow itself due to splicing or because it is the
# only read assigned to a multi-isoform gene. Only in the latter case the read
# should be considered processed as we are moving fragment by fragment, isoform
# by isoform, and read by read.
isoform != next_isoform || read != next_read { process_read() }
# Process fragments after gathering all related data.
fragment != next_fragment { process_fragment() }
############################
# Input parsing (ii): Data #
############################
# Parse new read/fragment data.
{
read_distance = $read_distance_col
gene = $gene_col
fragment_distance = $fragment_distance_col
}
##################################
# Multi-isoform genes accounting #
##################################
# Keep track of genes expressing more than one isoform to exclude from fragment
# distance sampling to avoid bias towards shorter fragments.
!(gene in reported_multi_iso_genes) {
if (gene in gene2isoform) {
if (isoform != gene2isoform[gene]) {
print gene > multi_isoform_genes_txt
reported_multi_iso_genes[gene]
}
} else {
gene2isoform[gene] = isoform
}
}
########################
# Finalization/Cleanup #
########################
# Don't forget to process the final lines as well.
END {
process_read()
process_fragment()
}
#############
# Functions #
#############
# Define what to do when a read's data has been read.
function process_read() {
# Only consider read for the first isoform (all read-level data should be
# identical) to avoid double-counting.
if (isoform == fragment_isoform) {
# Keep track of read counts per fragment.
n_reads++
# Keep track of the read distances within the fragment, but skip one read
# anchoring the fragment itself as its position won't require sampling.
# Note: If several reads share the same position, zero-distance reads could
# still be recorded and sampled, as intended.
if (anchor_read_skipped || read_distance != fragment_distance) {
print read_distance - fragment_distance > read_dists_anchors_skipped_txt
} else { # !anchors_skipped && read_distance == fragment_distance
anchor_read_skipped = true
}
}
read = next_read
if(fragment == next_fragment) { isoform = next_isoform }
}
# Define what to do when a fragment's data has been read.
function process_fragment() {
# Keep track of distances of unambiguously assigned fragments.
if (isoform == fragment_isoform) {
print gene, fragment_distance
}
# Keep track of read counts per fragment.
print n_reads > n_reads_per_fragment_txt
# Initialise next fragment.
fragment = next_fragment
fragment_isoform = isoform = next_isoform
n_reads = anchor_read_skipped = false
}