pywombat 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywombat/cli.py +124 -0
- {pywombat-0.3.0.dist-info → pywombat-0.4.0.dist-info}/METADATA +1 -1
- pywombat-0.4.0.dist-info/RECORD +6 -0
- pywombat-0.3.0.dist-info/RECORD +0 -6
- {pywombat-0.3.0.dist-info → pywombat-0.4.0.dist-info}/WHEEL +0 -0
- {pywombat-0.3.0.dist-info → pywombat-0.4.0.dist-info}/entry_points.txt +0 -0
pywombat/cli.py
CHANGED
|
@@ -40,6 +40,11 @@ import yaml
|
|
|
40
40
|
type=click.Path(exists=True, path_type=Path),
|
|
41
41
|
help="Filter configuration YAML file to apply quality and impact filters.",
|
|
42
42
|
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"--debug",
|
|
45
|
+
type=str,
|
|
46
|
+
help="Debug mode: show rows matching chrom:pos (e.g., chr11:70486013). Displays #CHROM, POS, VEP_SYMBOL, and columns from filter expression.",
|
|
47
|
+
)
|
|
43
48
|
def cli(
|
|
44
49
|
input_file: Path,
|
|
45
50
|
output: Optional[str],
|
|
@@ -47,6 +52,7 @@ def cli(
|
|
|
47
52
|
verbose: bool,
|
|
48
53
|
pedigree: Optional[Path],
|
|
49
54
|
filter_config: Optional[Path],
|
|
55
|
+
debug: Optional[str],
|
|
50
56
|
):
|
|
51
57
|
"""
|
|
52
58
|
Wombat: A tool for processing bcftools tabulated TSV files.
|
|
@@ -94,6 +100,11 @@ def cli(
|
|
|
94
100
|
click.echo(f"Reading filter config: {filter_config}", err=True)
|
|
95
101
|
filter_config_data = load_filter_config(filter_config)
|
|
96
102
|
|
|
103
|
+
# Debug mode: show specific variant
|
|
104
|
+
if debug:
|
|
105
|
+
debug_variant(input_file, pedigree_df, filter_config_data, debug, verbose)
|
|
106
|
+
return
|
|
107
|
+
|
|
97
108
|
# Determine output prefix
|
|
98
109
|
if output is None:
|
|
99
110
|
# Generate default output prefix from input filename
|
|
@@ -147,6 +158,95 @@ def cli(
|
|
|
147
158
|
raise click.Abort()
|
|
148
159
|
|
|
149
160
|
|
|
161
|
+
def debug_variant(
|
|
162
|
+
input_file: Path,
|
|
163
|
+
pedigree_df: Optional[pl.DataFrame],
|
|
164
|
+
filter_config: Optional[dict],
|
|
165
|
+
debug_pos: str,
|
|
166
|
+
verbose: bool,
|
|
167
|
+
):
|
|
168
|
+
"""Debug mode: display rows matching a specific chrom:pos."""
|
|
169
|
+
# Parse debug position
|
|
170
|
+
if ":" not in debug_pos:
|
|
171
|
+
click.echo(
|
|
172
|
+
"Error: Debug position must be in format 'chrom:pos' (e.g., chr11:70486013)",
|
|
173
|
+
err=True,
|
|
174
|
+
)
|
|
175
|
+
raise click.Abort()
|
|
176
|
+
|
|
177
|
+
chrom, pos = debug_pos.split(":", 1)
|
|
178
|
+
try:
|
|
179
|
+
pos = int(pos)
|
|
180
|
+
except ValueError:
|
|
181
|
+
click.echo(f"Error: Position must be an integer, got '{pos}'", err=True)
|
|
182
|
+
raise click.Abort()
|
|
183
|
+
|
|
184
|
+
if verbose:
|
|
185
|
+
click.echo(f"Debug mode: searching for {chrom}:{pos}", err=True)
|
|
186
|
+
|
|
187
|
+
# Read and format the data
|
|
188
|
+
df = pl.read_csv(input_file, separator="\t")
|
|
189
|
+
formatted_df = format_bcftools_tsv(df, pedigree_df)
|
|
190
|
+
|
|
191
|
+
# Filter to matching rows
|
|
192
|
+
matching_rows = formatted_df.filter(
|
|
193
|
+
(pl.col("#CHROM") == chrom) & (pl.col("POS") == pos)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if matching_rows.shape[0] == 0:
|
|
197
|
+
click.echo(f"No rows found matching {chrom}:{pos}", err=True)
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# Determine which columns to display
|
|
201
|
+
columns_to_show = ["#CHROM", "POS"]
|
|
202
|
+
|
|
203
|
+
# Add VEP_SYMBOL if it exists
|
|
204
|
+
if "VEP_SYMBOL" in matching_rows.columns:
|
|
205
|
+
columns_to_show.append("VEP_SYMBOL")
|
|
206
|
+
|
|
207
|
+
# Extract column names from expression if filter config provided
|
|
208
|
+
if filter_config and "expression" in filter_config:
|
|
209
|
+
expression = filter_config["expression"]
|
|
210
|
+
# Extract column names from expression using regex
|
|
211
|
+
# Match patterns like "column_name" before operators
|
|
212
|
+
column_pattern = r"\b([A-Za-z_][A-Za-z0-9_]*)\b\s*[=!<>]"
|
|
213
|
+
found_columns = re.findall(column_pattern, expression)
|
|
214
|
+
|
|
215
|
+
for col in found_columns:
|
|
216
|
+
if col in matching_rows.columns and col not in columns_to_show:
|
|
217
|
+
columns_to_show.append(col)
|
|
218
|
+
|
|
219
|
+
# Select only the columns we want to display
|
|
220
|
+
display_df = matching_rows.select(
|
|
221
|
+
[col for col in columns_to_show if col in matching_rows.columns]
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Replace null and NaN values with <null> and <NaN> for display
|
|
225
|
+
for col in display_df.columns:
|
|
226
|
+
if display_df[col].dtype in [pl.Float32, pl.Float64]:
|
|
227
|
+
# For numeric columns, handle both NaN and null
|
|
228
|
+
display_df = display_df.with_columns(
|
|
229
|
+
pl.when(pl.col(col).is_null())
|
|
230
|
+
.then(pl.lit("<null>"))
|
|
231
|
+
.when(pl.col(col).is_nan())
|
|
232
|
+
.then(pl.lit("<NaN>"))
|
|
233
|
+
.otherwise(pl.col(col).cast(pl.Utf8))
|
|
234
|
+
.alias(col)
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
# For non-numeric columns, only handle null
|
|
238
|
+
display_df = display_df.with_columns(
|
|
239
|
+
pl.when(pl.col(col).is_null())
|
|
240
|
+
.then(pl.lit("<null>"))
|
|
241
|
+
.otherwise(pl.col(col).cast(pl.Utf8))
|
|
242
|
+
.alias(col)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Display the results
|
|
246
|
+
click.echo(f"\nFound {matching_rows.shape[0]} row(s) matching {chrom}:{pos}:\n")
|
|
247
|
+
click.echo(display_df.write_csv(separator="\t"))
|
|
248
|
+
|
|
249
|
+
|
|
150
250
|
def load_filter_config(config_path: Path) -> dict:
|
|
151
251
|
"""Load and parse filter configuration from YAML file."""
|
|
152
252
|
with open(config_path, "r") as f:
|
|
@@ -366,6 +466,30 @@ def parse_impact_filter_expression(expression: str, df: pl.DataFrame) -> pl.Expr
|
|
|
366
466
|
if col_name not in df.columns:
|
|
367
467
|
raise ValueError(f"Column '{col_name}' not found in dataframe")
|
|
368
468
|
|
|
469
|
+
# Check for null value
|
|
470
|
+
if value.upper() == "NULL":
|
|
471
|
+
col_expr = pl.col(col_name)
|
|
472
|
+
if op == "=":
|
|
473
|
+
return col_expr.is_null()
|
|
474
|
+
elif op == "!=":
|
|
475
|
+
return ~col_expr.is_null()
|
|
476
|
+
else:
|
|
477
|
+
raise ValueError(
|
|
478
|
+
f"Operator '{op}' not supported for null comparison, use = or !="
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# Check for NaN value
|
|
482
|
+
if value.upper() == "NAN":
|
|
483
|
+
col_expr = pl.col(col_name).cast(pl.Float64, strict=False)
|
|
484
|
+
if op == "=":
|
|
485
|
+
return col_expr.is_nan()
|
|
486
|
+
elif op == "!=":
|
|
487
|
+
return ~col_expr.is_nan()
|
|
488
|
+
else:
|
|
489
|
+
raise ValueError(
|
|
490
|
+
f"Operator '{op}' not supported for NaN comparison, use = or !="
|
|
491
|
+
)
|
|
492
|
+
|
|
369
493
|
# Try to convert value to number, otherwise treat as string
|
|
370
494
|
try:
|
|
371
495
|
value_num = float(value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pywombat
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A CLI tool for processing and filtering bcftools tabulated TSV files with pedigree support
|
|
5
5
|
Project-URL: Homepage, https://github.com/bourgeron-lab/pywombat
|
|
6
6
|
Project-URL: Repository, https://github.com/bourgeron-lab/pywombat
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
pywombat/__init__.py,sha256=iIPN9vJtsIUhl_DiKNnknxCamLinfayodLLFK8y-aJg,54
|
|
2
|
+
pywombat/cli.py,sha256=dg38E39VpdJhKQt3aGSHwSiLWn1W8JnUkcsy3ZUHD5w,43518
|
|
3
|
+
pywombat-0.4.0.dist-info/METADATA,sha256=ZKPTIp9ud2AIVbcujg4ciq900DX-UkGs5oafa41jxTQ,4982
|
|
4
|
+
pywombat-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
+
pywombat-0.4.0.dist-info/entry_points.txt,sha256=Vt7U2ypbiEgCBlEV71ZPk287H5_HKmPBT4iBu6duEcE,44
|
|
6
|
+
pywombat-0.4.0.dist-info/RECORD,,
|
pywombat-0.3.0.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
pywombat/__init__.py,sha256=iIPN9vJtsIUhl_DiKNnknxCamLinfayodLLFK8y-aJg,54
|
|
2
|
-
pywombat/cli.py,sha256=kv03IFXcwe9pdv-KyoT5Cu1pJ9r-O7ww-Kh0ZT2ysa4,38920
|
|
3
|
-
pywombat-0.3.0.dist-info/METADATA,sha256=eASint-XgzgUGWshtZYr4nekDCs-VKSTilHLRupH5ic,4982
|
|
4
|
-
pywombat-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
-
pywombat-0.3.0.dist-info/entry_points.txt,sha256=Vt7U2ypbiEgCBlEV71ZPk287H5_HKmPBT4iBu6duEcE,44
|
|
6
|
-
pywombat-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|