oxymetag 1.0.0__py3-none-any.whl → 1.1.1__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.
- oxymetag/__init__.py +1 -1
- oxymetag/cli.py +15 -13
- oxymetag/core.py +114 -15
- oxymetag/data/VTML20.out +33 -0
- oxymetag/data/nucleotide.out +9 -0
- oxymetag/data/oxygen_model.rds +0 -0
- oxymetag/data/oxymetag_pfams_n117.dmnd +0 -0
- oxymetag/data/oxymetag_pfams_n117_db +0 -0
- oxymetag/data/oxymetag_pfams_n117_db.dbtype +0 -0
- oxymetag/data/oxymetag_pfams_n117_db.index +23972 -0
- oxymetag/data/oxymetag_pfams_n117_db.lookup +23972 -0
- oxymetag/data/oxymetag_pfams_n117_db.source +1 -0
- oxymetag/data/oxymetag_pfams_n117_db_h +0 -0
- oxymetag/data/oxymetag_pfams_n117_db_h.dbtype +0 -0
- oxymetag/data/oxymetag_pfams_n117_db_h.index +23972 -0
- oxymetag/scripts/predict_oxygen.R +86 -38
- oxymetag/utils.py +32 -14
- {oxymetag-1.0.0.dist-info → oxymetag-1.1.1.dist-info}/METADATA +116 -52
- oxymetag-1.1.1.dist-info/RECORD +29 -0
- oxymetag-1.0.0.dist-info/RECORD +0 -18
- {oxymetag-1.0.0.dist-info → oxymetag-1.1.1.dist-info}/LICENSE +0 -0
- {oxymetag-1.0.0.dist-info → oxymetag-1.1.1.dist-info}/WHEEL +0 -0
- {oxymetag-1.0.0.dist-info → oxymetag-1.1.1.dist-info}/entry_points.txt +0 -0
- {oxymetag-1.0.0.dist-info → oxymetag-1.1.1.dist-info}/top_level.txt +0 -0
|
@@ -25,7 +25,7 @@ bitcut <- as.numeric(args[7])
|
|
|
25
25
|
|
|
26
26
|
predict_oxygen <- function(input_dir, output_file, package_data_dir, mode, idcut, ecut, bitcut) {
|
|
27
27
|
|
|
28
|
-
# Load package data files
|
|
28
|
+
# Load package data files
|
|
29
29
|
map_file <- file.path(package_data_dir, "pfam_headers_table.txt")
|
|
30
30
|
lengths_file <- file.path(package_data_dir, "pfam_lengths.tsv")
|
|
31
31
|
model_file <- file.path(package_data_dir, "oxygen_model.rds")
|
|
@@ -47,19 +47,45 @@ predict_oxygen <- function(input_dir, output_file, package_data_dir, mode, idcut
|
|
|
47
47
|
|
|
48
48
|
# Load the trained model and oxygen classifications
|
|
49
49
|
oxygen_model <- readRDS(model_file)
|
|
50
|
-
|
|
51
|
-
# Get aerobic and anaerobic pfam lists (you'll need to define these)
|
|
52
50
|
oxygen_pfams <- read.csv(pfams_file, stringsAsFactors = FALSE)
|
|
53
51
|
aerobic_pfams <- oxygen_pfams %>% filter(Oxygen == "aerobic")
|
|
54
52
|
anaerobic_pfams <- oxygen_pfams %>% filter(Oxygen == "anaerobic")
|
|
55
53
|
|
|
56
|
-
#
|
|
57
|
-
|
|
54
|
+
# Determine method and file pattern based on mode
|
|
55
|
+
if (mode == "modern") {
|
|
56
|
+
method <- "diamond"
|
|
57
|
+
file_pattern <- "*_diamond.tsv"
|
|
58
|
+
} else if (mode == "ancient") {
|
|
59
|
+
method <- "mmseqs2"
|
|
60
|
+
file_pattern <- "*_mmseqs.tsv"
|
|
61
|
+
} else if (mode == "custom") {
|
|
62
|
+
# For custom mode, try to detect which files exist
|
|
63
|
+
diamond_files <- list.files(input_dir, pattern = "*_diamond.tsv", full.names = TRUE)
|
|
64
|
+
mmseqs_files <- list.files(input_dir, pattern = "*_mmseqs.tsv", full.names = TRUE)
|
|
65
|
+
|
|
66
|
+
if (length(diamond_files) > 0 && length(mmseqs_files) == 0) {
|
|
67
|
+
method <- "diamond"
|
|
68
|
+
file_pattern <- "*_diamond.tsv"
|
|
69
|
+
} else if (length(mmseqs_files) > 0 && length(diamond_files) == 0) {
|
|
70
|
+
method <- "mmseqs2"
|
|
71
|
+
file_pattern <- "*_mmseqs.tsv"
|
|
72
|
+
} else if (length(diamond_files) > 0 && length(mmseqs_files) > 0) {
|
|
73
|
+
stop("Both DIAMOND and MMseqs2 files found. Please specify input directory with only one type.")
|
|
74
|
+
} else {
|
|
75
|
+
stop("No profiling output files found in input directory.")
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
stop(paste("Unknown mode:", mode))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
files <- list.files(input_dir, pattern = file_pattern, full.names = TRUE)
|
|
58
82
|
|
|
59
83
|
if (length(files) == 0) {
|
|
60
|
-
stop(paste("No
|
|
84
|
+
stop(paste("No", file_pattern, "files found in", input_dir))
|
|
61
85
|
}
|
|
62
86
|
|
|
87
|
+
message("Processing ", length(files), " files using ", method, " output format")
|
|
88
|
+
|
|
63
89
|
# Initialize results dataframe
|
|
64
90
|
results <- data.frame(
|
|
65
91
|
SampleID = character(length(files)),
|
|
@@ -75,29 +101,50 @@ predict_oxygen <- function(input_dir, output_file, package_data_dir, mode, idcut
|
|
|
75
101
|
|
|
76
102
|
# Extract sample ID from filename
|
|
77
103
|
sample_id <- basename(files[i])
|
|
78
|
-
sample_id <- gsub("_diamond.tsv$", "", sample_id)
|
|
104
|
+
sample_id <- gsub("_diamond.tsv$|_mmseqs.tsv$", "", sample_id)
|
|
79
105
|
results$SampleID[i] <- sample_id
|
|
80
106
|
|
|
81
|
-
# Read and filter
|
|
107
|
+
# Read and filter profiling output
|
|
82
108
|
if (file.size(files[i]) == 0) {
|
|
83
109
|
message("Warning: Empty file ", files[i])
|
|
84
110
|
next
|
|
85
111
|
}
|
|
86
112
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
# Read data based on method
|
|
114
|
+
if (method == "diamond") {
|
|
115
|
+
d <- read.table(files[i], stringsAsFactors = FALSE) %>%
|
|
116
|
+
set_names(c("qseqid", "sseqid", "pident", "length", "qstart", "qend",
|
|
117
|
+
"sstart", "send", "evalue", "bitscore")) %>%
|
|
118
|
+
left_join(map, by = c("sseqid" = "Header")) %>%
|
|
119
|
+
group_by(qseqid) %>%
|
|
120
|
+
slice_max(bitscore, n = 1, with_ties = FALSE) %>%
|
|
121
|
+
ungroup()
|
|
122
|
+
|
|
123
|
+
# Apply filtering based on mode
|
|
124
|
+
if (mode == "modern") {
|
|
125
|
+
d <- d %>% filter(pident >= 60, evalue < 0.001, bitscore >= 50)
|
|
126
|
+
} else if (mode == "custom") {
|
|
127
|
+
d <- d %>% filter(pident >= idcut, evalue < ecut, bitscore >= bitcut)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
} else if (method == "mmseqs2") {
|
|
131
|
+
d <- read.table(files[i], stringsAsFactors = FALSE) %>%
|
|
132
|
+
set_names(c("query", "target", "pident", "length", "mismatch", "gapopen",
|
|
133
|
+
"qstart", "qend", "tstart", "tend", "evalue", "bitscore",
|
|
134
|
+
"qlen", "tlen", "cigar", "qaln", "taln")) %>%
|
|
135
|
+
mutate(pident = pident * 100) %>%
|
|
136
|
+
left_join(map, by = c("target" = "Header")) %>%
|
|
137
|
+
group_by(query) %>%
|
|
138
|
+
slice_max(bitscore, n = 1, with_ties = FALSE) %>%
|
|
139
|
+
ungroup() %>%
|
|
140
|
+
filter(Pfam %in% oxygen_pfams$Pfam)
|
|
141
|
+
|
|
142
|
+
# Apply filtering based on mode
|
|
143
|
+
if (mode == "ancient") {
|
|
144
|
+
d <- d %>% filter(pident >= 86, evalue < 0.001, bitscore >= 50)
|
|
145
|
+
} else if (mode == "custom") {
|
|
146
|
+
d <- d %>% filter(pident >= idcut, evalue < ecut, bitscore >= bitcut)
|
|
147
|
+
}
|
|
101
148
|
}
|
|
102
149
|
|
|
103
150
|
if (nrow(d) == 0) {
|
|
@@ -108,45 +155,46 @@ predict_oxygen <- function(input_dir, output_file, package_data_dir, mode, idcut
|
|
|
108
155
|
next
|
|
109
156
|
}
|
|
110
157
|
|
|
111
|
-
# Join with pfam mapping
|
|
112
|
-
d <- d %>% left_join(map, by = c("sseqid" = "Header"))
|
|
113
|
-
|
|
114
158
|
# Count pfams
|
|
115
159
|
pf_count <- as.data.frame(table(d$Pfam))
|
|
116
|
-
results$Pfams[i] <- nrow(pf_count)
|
|
117
160
|
results$aerobe_pfams[i] <- sum(as.character(pf_count$Var1) %in% aerobic_pfams$Pfam)
|
|
118
161
|
results$anaerobe_pfams[i] <- sum(as.character(pf_count$Var1) %in% anaerobic_pfams$Pfam)
|
|
119
162
|
|
|
120
163
|
# Calculate gene hits and length correction
|
|
121
164
|
gene.hits <- d %>%
|
|
122
165
|
group_by(Pfam) %>%
|
|
123
|
-
summarise(total_count = n())
|
|
166
|
+
summarise(total_count = n(), .groups = 'drop')
|
|
124
167
|
|
|
125
168
|
gene.hit.length.correction <- gene.hits %>%
|
|
126
|
-
left_join(
|
|
127
|
-
mutate(RPK = total_count / (1000*Gene.length)) %>%
|
|
128
|
-
left_join(
|
|
169
|
+
left_join(pfam_gene_length, by = "Pfam") %>%
|
|
170
|
+
mutate(RPK = total_count / (1000 * Gene.length)) %>%
|
|
171
|
+
left_join(oxygen_pfams, by = "Pfam")
|
|
129
172
|
|
|
130
173
|
# Sum by oxygen type
|
|
131
174
|
oxygen_rpk <- gene.hit.length.correction %>%
|
|
132
175
|
group_by(Oxygen) %>%
|
|
133
|
-
summarize(RPKsum = sum(RPK))
|
|
176
|
+
summarize(RPKsum = sum(RPK, na.rm = TRUE), .groups = 'drop')
|
|
134
177
|
|
|
135
|
-
# Calculate
|
|
136
|
-
|
|
178
|
+
# Calculate ratio (aerobe/anaerobe)
|
|
179
|
+
aerobe_rpk <- oxygen_rpk$RPKsum[oxygen_rpk$Oxygen == "aerobic"]
|
|
180
|
+
anaerobe_rpk <- oxygen_rpk$RPKsum[oxygen_rpk$Oxygen == "anaerobic"]
|
|
181
|
+
|
|
182
|
+
if (length(anaerobe_rpk) == 0 || anaerobe_rpk == 0) {
|
|
183
|
+
results$ratio[i] <- ifelse(length(aerobe_rpk) > 0 && aerobe_rpk > 0, Inf, 0)
|
|
184
|
+
} else {
|
|
185
|
+
results$ratio[i] <- aerobe_rpk / anaerobe_rpk
|
|
186
|
+
}
|
|
137
187
|
|
|
138
|
-
# Processing message
|
|
139
188
|
message("Processed sample ", i, "/", length(files), ": ", sample_id)
|
|
140
189
|
}
|
|
141
190
|
|
|
142
191
|
# Make predictions using the GAM model
|
|
143
192
|
new_data <- data.frame(ratio = results$ratio)
|
|
144
|
-
results$Per_aerobe <- predict(oxygen_model, newdata = new_data)
|
|
193
|
+
results$Per_aerobe <- predict(oxygen_model, newdata = new_data, type = "response")
|
|
145
194
|
|
|
146
195
|
# Constrain predictions to 0-100% and set to 100% if ratio > 35
|
|
147
196
|
results <- results %>%
|
|
148
|
-
mutate(Per_aerobe =
|
|
149
|
-
mutate(Per_aerobe = ifelse(Per_aerobe < 0, 0, Per_aerobe)) %>%
|
|
197
|
+
mutate(Per_aerobe = pmax(0, pmin(100, Per_aerobe))) %>%
|
|
150
198
|
mutate(Per_aerobe = ifelse(ratio > 35, 100, Per_aerobe))
|
|
151
199
|
|
|
152
200
|
# Save results
|
|
@@ -157,4 +205,4 @@ predict_oxygen <- function(input_dir, output_file, package_data_dir, mode, idcut
|
|
|
157
205
|
}
|
|
158
206
|
|
|
159
207
|
# Run the function
|
|
160
|
-
predict_oxygen(input_dir, output_file, package_data_dir, mode, idcut, ecut, bitcut)
|
|
208
|
+
predict_oxygen(input_dir, output_file, package_data_dir, mode, idcut, ecut, bitcut)
|
oxymetag/utils.py
CHANGED
|
@@ -4,19 +4,49 @@ Utility functions for OxyMetaG
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import subprocess
|
|
7
|
-
import pkg_resources
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
import logging
|
|
10
9
|
|
|
10
|
+
# Use importlib.resources instead of deprecated pkg_resources
|
|
11
|
+
try:
|
|
12
|
+
from importlib.resources import files
|
|
13
|
+
use_importlib = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
use_importlib = False
|
|
16
|
+
|
|
11
17
|
logger = logging.getLogger('oxymetag')
|
|
12
18
|
|
|
13
19
|
class OxyMetaGError(Exception):
|
|
14
20
|
"""Custom exception for OxyMetaG errors"""
|
|
15
21
|
pass
|
|
16
22
|
|
|
23
|
+
def get_package_data_path(filename: str) -> str:
|
|
24
|
+
"""Get path to package data files"""
|
|
25
|
+
if use_importlib:
|
|
26
|
+
try:
|
|
27
|
+
package_files = files('oxymetag')
|
|
28
|
+
if filename.startswith('../'):
|
|
29
|
+
parts = filename.split('/')
|
|
30
|
+
for part in parts:
|
|
31
|
+
if part == '..':
|
|
32
|
+
package_files = package_files.parent
|
|
33
|
+
elif part and part != '.':
|
|
34
|
+
package_files = package_files / part
|
|
35
|
+
return str(package_files)
|
|
36
|
+
else:
|
|
37
|
+
return str(package_files / 'data' / filename)
|
|
38
|
+
except:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
package_dir = Path(__file__).parent
|
|
42
|
+
if filename.startswith('../'):
|
|
43
|
+
return str(package_dir / filename)
|
|
44
|
+
else:
|
|
45
|
+
return str(package_dir / 'data' / filename)
|
|
46
|
+
|
|
17
47
|
def check_dependencies():
|
|
18
48
|
"""Check if required external tools are available"""
|
|
19
|
-
required_tools = ['kraken2', 'diamond', 'Rscript']
|
|
49
|
+
required_tools = ['kraken2', 'diamond', 'mmseqs', 'Rscript']
|
|
20
50
|
missing_tools = []
|
|
21
51
|
|
|
22
52
|
for tool in required_tools:
|
|
@@ -26,14 +56,6 @@ def check_dependencies():
|
|
|
26
56
|
if missing_tools:
|
|
27
57
|
raise OxyMetaGError(f"Missing required tools: {', '.join(missing_tools)}")
|
|
28
58
|
|
|
29
|
-
def get_package_data_path(filename: str) -> str:
|
|
30
|
-
"""Get path to package data files"""
|
|
31
|
-
try:
|
|
32
|
-
return pkg_resources.resource_filename('oxymetag', f'data/{filename}')
|
|
33
|
-
except:
|
|
34
|
-
package_dir = Path(__file__).parent
|
|
35
|
-
return str(package_dir / 'data' / filename)
|
|
36
|
-
|
|
37
59
|
def run_kraken2_setup():
|
|
38
60
|
"""Download and set up standard Kraken2 database without fungi"""
|
|
39
61
|
logger.info("Setting up Kraken2 database (bacteria, archaea, viral)...")
|
|
@@ -42,13 +64,11 @@ def run_kraken2_setup():
|
|
|
42
64
|
db_path.mkdir(exist_ok=True)
|
|
43
65
|
|
|
44
66
|
try:
|
|
45
|
-
# Download taxonomy
|
|
46
67
|
cmd = ['kraken2-build', '--download-taxonomy', '--db', str(db_path)]
|
|
47
68
|
logger.info("Downloading taxonomy...")
|
|
48
69
|
subprocess.run(cmd, check=True)
|
|
49
70
|
logger.info("Taxonomy downloaded successfully")
|
|
50
71
|
|
|
51
|
-
# Download libraries (excluding fungi)
|
|
52
72
|
libraries = ['bacteria', 'archaea', 'viral']
|
|
53
73
|
for lib in libraries:
|
|
54
74
|
cmd = ['kraken2-build', '--download-library', lib, '--db', str(db_path)]
|
|
@@ -56,12 +76,10 @@ def run_kraken2_setup():
|
|
|
56
76
|
subprocess.run(cmd, check=True)
|
|
57
77
|
logger.info(f"{lib} library downloaded successfully")
|
|
58
78
|
|
|
59
|
-
# Build database
|
|
60
79
|
cmd = ['kraken2-build', '--build', '--db', str(db_path), '--threads', '48']
|
|
61
80
|
logger.info("Building Kraken2 database...")
|
|
62
81
|
subprocess.run(cmd, check=True)
|
|
63
82
|
|
|
64
|
-
# Clean up temporary files to save space
|
|
65
83
|
cmd = ['kraken2-build', '--clean', '--db', str(db_path)]
|
|
66
84
|
logger.info("Cleaning up temporary files...")
|
|
67
85
|
subprocess.run(cmd, check=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: oxymetag
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Oxygen metabolism profiling from metagenomic data
|
|
5
5
|
Home-page: https://github.com/cliffbueno/oxymetag
|
|
6
6
|
Author: Clifton P. Bueno de Mesquita
|
|
@@ -25,9 +25,9 @@ Requires-Dist: numpy >=1.20.0
|
|
|
25
25
|
|
|
26
26
|
Oxygen metabolism profiling from metagenomic data using Pfam domains. OxyMetaG predicts the percent relative abundance of aerobic bacteria in metagenomic reads based on the ratio of abundances of a set of 20 Pfams. It is recommended to use a HPC cluster or server rather than laptop to run OxyMetaG due to memory requirements, particularly for the step of extracting bacterial reads. If you already have bacterial reads, the "profile" and "predict" functions will run quickly on a laptop.
|
|
27
27
|
|
|
28
|
-
If you are working with modern metagenomes, we recommend first quality filtering the raw reads with your method of choice and standard practices, and then extracting bacterial reads with Kraken2 and KrakenTools, which is performed with the OxyMetaG extract function.
|
|
28
|
+
If you are working with modern metagenomes, we recommend first quality filtering the raw reads with your method of choice and standard practices, and then extracting bacterial reads with Kraken2 and KrakenTools, which is performed with the OxyMetaG extract function. For profiling modern metagenomes, use DIAMOND blastx with the default `-m diamond` mode for the profile step. You can also use `-m custom` in the predict step to test different hit cutoffs.
|
|
29
29
|
|
|
30
|
-
If you are working with ancient metagenomes, we recommend first quality filtering the raw reads with your method of choice and standard practices, and then extracting bacterial reads with a workflow optimized for ancient DNA, such as the
|
|
30
|
+
If you are working with ancient metagenomes, we recommend first quality filtering the raw reads with your method of choice and standard practices, and then extracting bacterial reads with a workflow optimized for ancient DNA, such as the read mapping approach employed by De Sanctis et al. (2025). For profiling ancient metagenomes, use MMseqs2 with `-m mmseqs2` for the profile step and `-m ancient` for the predict step. The ancient mode uses parameters optimized for ancient DNA along with 97 decoy Pfams to reduce instances of false positives. We are still working on optimizing the methods for ancient DNA, which will be released as v2.0.0.
|
|
31
31
|
|
|
32
32
|
## Installation
|
|
33
33
|
|
|
@@ -46,16 +46,22 @@ conda env create -f environment.yml
|
|
|
46
46
|
conda activate oxymetag
|
|
47
47
|
|
|
48
48
|
# Install OxyMetaG
|
|
49
|
-
pip install
|
|
49
|
+
pip install -e .
|
|
50
|
+
|
|
51
|
+
# Index the MMseqs2 database (one-time setup, ~5-10 minutes)
|
|
52
|
+
mmseqs createindex oxymetag/data/oxymetag_pfams_n117_db tmp
|
|
50
53
|
```
|
|
51
54
|
|
|
55
|
+
**Note:** The MMseqs2 database indexing is optional but highly recommended for faster searches.
|
|
56
|
+
|
|
52
57
|
### Using Pip
|
|
53
58
|
|
|
54
59
|
First install external dependencies:
|
|
55
60
|
- Kraken2
|
|
56
61
|
- DIAMOND
|
|
62
|
+
- MMseqs2
|
|
57
63
|
- KrakenTools
|
|
58
|
-
- R with mgcv and
|
|
64
|
+
- R with mgcv, dplyr, tidyr, and rlang packages
|
|
59
65
|
|
|
60
66
|
Then install OxyMetaG:
|
|
61
67
|
```bash
|
|
@@ -64,30 +70,37 @@ pip install oxymetag
|
|
|
64
70
|
|
|
65
71
|
## Quick Start
|
|
66
72
|
|
|
67
|
-
###
|
|
73
|
+
### Modern DNA workflow
|
|
74
|
+
|
|
68
75
|
```bash
|
|
76
|
+
# 1. Setup Kraken2 database (one-time)
|
|
69
77
|
oxymetag setup
|
|
70
|
-
```
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
oxymetag extract -i sample1_R1.fastq.gz sample1_R2.fastq.gz -o BactReads -t 48
|
|
75
|
-
```
|
|
79
|
+
# 2. Extract bacterial reads
|
|
80
|
+
oxymetag extract -i sample1_R1.fastq.gz -o BactReads -t 48
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
# 3. Profile samples with DIAMOND
|
|
83
|
+
oxymetag profile -i BactReads -o diamond_output -m diamond -t 8
|
|
84
|
+
|
|
85
|
+
# 4. Predict aerobe levels
|
|
86
|
+
oxymetag predict -i diamond_output -o per_aerobe_predictions.tsv -m modern
|
|
80
87
|
```
|
|
81
88
|
|
|
82
|
-
###
|
|
89
|
+
### Ancient DNA workflow
|
|
90
|
+
|
|
83
91
|
```bash
|
|
84
|
-
#
|
|
85
|
-
|
|
92
|
+
# 1. Extract bacterial reads with an ancient DNA-optimized workflow (not currently provided by OxyMetaG)
|
|
93
|
+
|
|
94
|
+
# 2. Profile samples with MMseqs2
|
|
95
|
+
oxymetag profile -i BactReads -o mmseqs_output -m mmseqs2 -t 8
|
|
86
96
|
|
|
87
|
-
#
|
|
88
|
-
oxymetag predict -i
|
|
97
|
+
# 3. Predict aerobe levels with ancient mode
|
|
98
|
+
oxymetag predict -i mmseqs_output -o per_aerobe_predictions.tsv -m ancient
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Custom parameters
|
|
89
102
|
|
|
90
|
-
|
|
103
|
+
```bash
|
|
91
104
|
oxymetag predict -i diamond_output -o per_aerobe_predictions.tsv -m custom --idcut 50 --bitcut 30 --ecut 0.01
|
|
92
105
|
```
|
|
93
106
|
|
|
@@ -98,11 +111,11 @@ oxymetag predict -i diamond_output -o per_aerobe_predictions.tsv -m custom --idc
|
|
|
98
111
|
|
|
99
112
|
**What it does:** Downloads and builds the standard Kraken2 database containing bacterial, archaeal, and viral genomes. This database is used by the `extract` command to identify bacterial sequences from metagenomic samples.
|
|
100
113
|
|
|
101
|
-
**Time:**
|
|
114
|
+
**Time:** Depends on internet speed and system performance, but will likely take several hours (4-20 hours typical).
|
|
102
115
|
|
|
103
|
-
**Output:** Creates a `kraken2_db/` directory with the standard database.
|
|
116
|
+
**Output:** Creates a `kraken2_db/` directory with the standard database (~90 GB).
|
|
104
117
|
|
|
105
|
-
Make sure you run oxymetag setup from the directory where you want the database to live, or plan to always specify the --kraken-db path when running extract. The database is quite large
|
|
118
|
+
Make sure you run oxymetag setup from the directory where you want the database to live, or plan to always specify the --kraken-db path when running extract. The database is quite large, so choose a location with sufficient storage.
|
|
106
119
|
|
|
107
120
|
---
|
|
108
121
|
|
|
@@ -114,7 +127,7 @@ Make sure you run oxymetag setup from the directory where you want the database
|
|
|
114
127
|
2. Uses KrakenTools to extract only the reads classified as bacterial
|
|
115
128
|
3. Outputs cleaned bacterial-only FASTQ files for downstream analysis
|
|
116
129
|
|
|
117
|
-
**Input:** Quality filtered metagenomic read FASTQ files (paired-end or merged)
|
|
130
|
+
**Input:** Quality filtered metagenomic read FASTQ files (paired-end or merged)
|
|
118
131
|
**Output:** Bacterial-only FASTQ files in `BactReads/` directory
|
|
119
132
|
|
|
120
133
|
**Arguments:**
|
|
@@ -126,22 +139,26 @@ Make sure you run oxymetag setup from the directory where you want the database
|
|
|
126
139
|
---
|
|
127
140
|
|
|
128
141
|
### oxymetag profile
|
|
129
|
-
**Function:** Profiles bacterial reads against oxygen metabolism protein domains.
|
|
142
|
+
**Function:** Profiles bacterial reads against oxygen metabolism protein domains using DIAMOND or MMseqs2.
|
|
130
143
|
|
|
131
144
|
**What it does:**
|
|
132
145
|
1. Takes bacterial-only reads from the `extract` step
|
|
133
|
-
2. Uses DIAMOND blastx to search against a curated database of
|
|
146
|
+
2. Uses DIAMOND blastx (for modern DNA) or MMseqs2 (for ancient DNA) to search against a curated database of Pfam domains related to oxygen metabolism
|
|
147
|
+
- DIAMOND mode: 20 target Pfams
|
|
148
|
+
- MMseqs2 mode: 20 target Pfams + 97 decoy Pfams (117 total) to reduce false positives
|
|
134
149
|
3. Identifies protein-coding sequences and their functional annotations
|
|
135
150
|
4. Creates detailed hit tables for each sample
|
|
136
151
|
|
|
137
|
-
**Input:** Bacterial FASTQ files (uses R1 or merged reads only)
|
|
138
|
-
**Output:**
|
|
152
|
+
**Input:** Bacterial FASTQ files (uses R1 or merged reads only)
|
|
153
|
+
**Output:** Alignment files (TSV format) in `diamond_output/` or `mmseqs_output/` directory
|
|
139
154
|
|
|
140
155
|
**Arguments:**
|
|
141
156
|
- `-i, --input`: Input directory with bacterial reads (default: BactReads)
|
|
142
|
-
- `-o, --output`: Output directory (default: diamond_output)
|
|
157
|
+
- `-o, --output`: Output directory (default: diamond_output or mmseqs_output depending on method)
|
|
143
158
|
- `-t, --threads`: Number of threads (default: 4)
|
|
144
|
-
-
|
|
159
|
+
- `-m, --method`: Profiling method - 'diamond' or 'mmseqs2' (default: diamond)
|
|
160
|
+
- `--diamond-db`: Custom DIAMOND database path (optional, for diamond method)
|
|
161
|
+
- `--mmseqs-db`: Custom MMseqs2 database path (optional, for mmseqs2 method)
|
|
145
162
|
|
|
146
163
|
---
|
|
147
164
|
|
|
@@ -149,17 +166,24 @@ Make sure you run oxymetag setup from the directory where you want the database
|
|
|
149
166
|
**Function:** Predicts aerobe abundance from protein domain profiles using machine learning.
|
|
150
167
|
|
|
151
168
|
**What it does:**
|
|
152
|
-
1. Processes DIAMOND output files with appropriate quality filters
|
|
153
|
-
2.
|
|
154
|
-
3.
|
|
155
|
-
4.
|
|
156
|
-
5.
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
1. Processes DIAMOND or MMseqs2 output files with appropriate quality filters
|
|
170
|
+
2. Selects the top hit per read based on bitscore
|
|
171
|
+
3. For MMseqs2 (ancient mode): filters out decoy Pfams after selecting top hits
|
|
172
|
+
4. Normalizes protein domain counts by gene length (reads per kilobase)
|
|
173
|
+
5. Calculates aerobic/anaerobic domain ratios for each sample
|
|
174
|
+
6. Applies a trained GAM (Generalized Additive Model) to predict percentage of aerobes
|
|
175
|
+
7. Outputs a table with the sampleID, # Pfams detected, and predicted % aerobic bacteria
|
|
176
|
+
|
|
177
|
+
**Input:** DIAMOND or MMseqs2 output directory from `profile` step
|
|
159
178
|
**Output:** Tab-separated file with aerobe predictions for each sample
|
|
160
179
|
|
|
180
|
+
**Mode determines input type:**
|
|
181
|
+
- `-m modern`: Uses DIAMOND output (default input: diamond_output/)
|
|
182
|
+
- `-m ancient`: Uses MMseqs2 output (default input: mmseqs_output/)
|
|
183
|
+
- `-m custom`: Auto-detects DIAMOND or MMseqs2 files in input directory
|
|
184
|
+
|
|
161
185
|
**Arguments:**
|
|
162
|
-
- `-i, --input`: Directory with
|
|
186
|
+
- `-i, --input`: Directory with profiling output (default: diamond_output for modern, mmseqs_output for ancient)
|
|
163
187
|
- `-o, --output`: Output file (default: per_aerobe_predictions.tsv)
|
|
164
188
|
- `-t, --threads`: Number of threads (default: 4)
|
|
165
189
|
- `-m, --mode`: Filtering mode - 'modern', 'ancient', or 'custom' (default: modern)
|
|
@@ -169,32 +193,56 @@ Make sure you run oxymetag setup from the directory where you want the database
|
|
|
169
193
|
|
|
170
194
|
## Filtering Modes
|
|
171
195
|
|
|
172
|
-
OxyMetaG includes three pre-configured filtering modes optimized for different types of DNA
|
|
196
|
+
OxyMetaG includes three pre-configured filtering modes optimized for different types of DNA. In any case, it is always recommended to try several different parameters (using -m custom) to check how sensitive the results are to the cutoffs.
|
|
173
197
|
|
|
174
198
|
### Modern DNA (default)
|
|
175
|
-
**Best for:** Modern environmental metagenomes
|
|
199
|
+
**Best for:** Modern environmental metagenomes
|
|
200
|
+
**Method:** DIAMOND blastx
|
|
201
|
+
**Filters:**
|
|
176
202
|
- Identity ≥ 60%
|
|
177
203
|
- Bitscore ≥ 50
|
|
178
204
|
- E-value ≤ 0.001
|
|
179
205
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
**Usage:**
|
|
207
|
+
```bash
|
|
208
|
+
oxymetag profile -m diamond
|
|
209
|
+
oxymetag predict -m modern
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Ancient DNA
|
|
213
|
+
**Best for:** Archaeological samples, paleogenomic data, degraded environmental DNA
|
|
214
|
+
**Method:** MMseqs2 with decoy Pfams
|
|
215
|
+
**Filters:**
|
|
216
|
+
- Identity ≥ 86%
|
|
217
|
+
- Bitscore ≥ 50
|
|
218
|
+
- E-value ≤ 0.001
|
|
219
|
+
|
|
220
|
+
**Note:** The ancient mode uses stricter identity cutoffs but employs 97 decoy Pfams to reduce false positives from damaged DNA. Reads matching decoys better than target Pfams are filtered out.
|
|
221
|
+
|
|
222
|
+
**Usage:**
|
|
223
|
+
```bash
|
|
224
|
+
oxymetag profile -m mmseqs2
|
|
225
|
+
oxymetag predict -m ancient
|
|
226
|
+
```
|
|
185
227
|
|
|
186
228
|
### Custom
|
|
187
229
|
**Best for:** Specialized applications or when you want to optimize parameters
|
|
188
230
|
- Specify your own `--idcut`, `--bitcut`, and `--ecut` values
|
|
231
|
+
- Auto-detects whether input is from DIAMOND or MMseqs2
|
|
189
232
|
- Useful for method development or unusual sample types
|
|
190
233
|
|
|
234
|
+
**Usage:**
|
|
235
|
+
```bash
|
|
236
|
+
oxymetag predict -m custom --idcut 50 --bitcut 30 --ecut 0.01
|
|
237
|
+
```
|
|
238
|
+
|
|
191
239
|
## Output
|
|
192
240
|
|
|
193
241
|
The final output (`per_aerobe_predictions.tsv`) contains:
|
|
194
242
|
- `SampleID`: Sample identifier extracted from filenames
|
|
195
243
|
- `ratio`: Aerobic/anaerobic domain ratio
|
|
196
|
-
- `aerobe_pfams`: Number of aerobic Pfam domains detected
|
|
197
|
-
- `anaerobe_pfams`: Number of anaerobic Pfam domains detected
|
|
244
|
+
- `aerobe_pfams`: Number of aerobic Pfam domains detected (from 20 target Pfams)
|
|
245
|
+
- `anaerobe_pfams`: Number of anaerobic Pfam domains detected (from 20 target Pfams)
|
|
198
246
|
- `Per_aerobe`: **Predicted percentage of aerobic bacteria (0-100%)**
|
|
199
247
|
|
|
200
248
|
## Biological Interpretation
|
|
@@ -212,17 +260,33 @@ The `Per_aerobe` value represents the predicted percentage of aerobic bacteria i
|
|
|
212
260
|
If you use OxyMetaG in your research, please cite:
|
|
213
261
|
|
|
214
262
|
```
|
|
215
|
-
Bueno de Mesquita, C.P., Stallard-Olivera, E., Fierer, N. (2025).
|
|
263
|
+
Bueno de Mesquita, C.P., Stallard-Olivera, E., Fierer, N. (2025).
|
|
264
|
+
Quantifying the oxygen preferences of bacterial communities using a
|
|
265
|
+
metagenome-based approach.
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Additional citations
|
|
269
|
+
|
|
270
|
+
If you use the **extract** function, also cite Kraken2 and KrakenTools:
|
|
271
|
+
```
|
|
272
|
+
Lu, J., Rincon, N., Wood, D.E. et al. Metagenome analysis using the Kraken
|
|
273
|
+
software suite. Nat Protoc 17, 2815–2839 (2022).
|
|
274
|
+
https://doi.org/10.1038/s41596-022-00738-y
|
|
216
275
|
```
|
|
217
|
-
If you use the extract function, also cite Kraken2 and KrakenTools:
|
|
218
276
|
|
|
277
|
+
If you use the **profile** function with DIAMOND (`-m diamond`), also cite:
|
|
219
278
|
```
|
|
220
|
-
|
|
279
|
+
Buchfink, B., Xie, C. & Huson, D. Fast and sensitive protein alignment using
|
|
280
|
+
DIAMOND. Nat Methods 12, 59–60 (2015).
|
|
281
|
+
https://doi.org/10.1038/nmeth.3176
|
|
221
282
|
```
|
|
222
|
-
If you use the profile function, also cite DIAMOND
|
|
223
283
|
|
|
284
|
+
If you use the **profile** function with MMseqs2 (`-m mmseqs2`), also cite:
|
|
224
285
|
```
|
|
225
|
-
|
|
286
|
+
Steinegger, M., Söding, J. MMseqs2 enables sensitive protein sequence
|
|
287
|
+
searching for the analysis of massive data sets. Nat Biotechnol 35,
|
|
288
|
+
1026–1028 (2017).
|
|
289
|
+
https://doi.org/10.1038/nbt.3988
|
|
226
290
|
```
|
|
227
291
|
|
|
228
292
|
## License
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
oxymetag/__init__.py,sha256=jmwbHrRvxQhdOGacT6-lojPH1Vf5r1BA0KWz2ycl0vk,441
|
|
2
|
+
oxymetag/cli.py,sha256=GIXCxpBUQIRWgcSyQNudAWyuVWAAnxK1DRGXG8jpVzQ,4877
|
|
3
|
+
oxymetag/core.py,sha256=zLLVT6XsNb7qFtDlobIh44xgnOzXauYBCL8nNLA4XSk,12496
|
|
4
|
+
oxymetag/utils.py,sha256=fxzKYNUpsuBnYZxlh8QMIEg5wktV6BatoLasfawTeW8,3178
|
|
5
|
+
oxymetag/data/.DS_Store,sha256=1lFlJ5EFymdzGAUAaI30vcaaLHt3F1LwpG7xILf9jsM,6148
|
|
6
|
+
oxymetag/data/Oxygen_pfams.csv,sha256=f3_CFPy235BxbX-2Ami8dJpTLQDImM8e_m87QcJvYKo,673
|
|
7
|
+
oxymetag/data/VTML20.out,sha256=V7F_3HDTtVccoSeuryUJObU5bY3dbfAYHx1TLFixqA8,1930
|
|
8
|
+
oxymetag/data/nucleotide.out,sha256=0IBDiBP7-Q-DJg_Ky3y7Rbv3Dw4s01Tu7S5MtxVJQo4,396
|
|
9
|
+
oxymetag/data/oxygen_model.rds,sha256=UP_3sIJJHEWCfiSTZPJrsVti91-mEUWOn7PgfNcWEDo,8240
|
|
10
|
+
oxymetag/data/oxymetag_pfams.dmnd,sha256=sSrkriGi-x4Ybf-pQtRRCFh1Wjm4rbQeX9FL9buDbAE,1472431
|
|
11
|
+
oxymetag/data/oxymetag_pfams_n117.dmnd,sha256=yIh1qK3aD23VDmG_jsGnx9I7ahh7vV5dSUnBb1y9Eek,15572193
|
|
12
|
+
oxymetag/data/oxymetag_pfams_n117_db,sha256=2C0jpmNzEd-_F6J2QaBW8SeNvivJ4L21nqYU9WLQNsU,12197517
|
|
13
|
+
oxymetag/data/oxymetag_pfams_n117_db.dbtype,sha256=3z9hmASpL9tAVxktxD3XSOp3itxSvEmM6AUkwBS4ERk,4
|
|
14
|
+
oxymetag/data/oxymetag_pfams_n117_db.index,sha256=bzgoZhWEsmFx6IU7aO5ArTMDBb7_1yepVSEP0oRt5Ms,423068
|
|
15
|
+
oxymetag/data/oxymetag_pfams_n117_db.lookup,sha256=XmvjvMlDr9CPklK-iZYv4LD9O9mVgMHR0s0zaVlowM4,919596
|
|
16
|
+
oxymetag/data/oxymetag_pfams_n117_db.source,sha256=qeG0mSUfLERK--Fq8hqNrStPEOgyEhXzIXZixYDADYs,27
|
|
17
|
+
oxymetag/data/oxymetag_pfams_n117_db_h,sha256=xDBPxG39tF1Ez70jd3XxqdyPxlmSr3B0sf9T6eFJ0Xo,1542665
|
|
18
|
+
oxymetag/data/oxymetag_pfams_n117_db_h.dbtype,sha256=QvSuuBwe-B93Hz3oq8qdz2aQHFdVMOdnLksRRkdK5lA,4
|
|
19
|
+
oxymetag/data/oxymetag_pfams_n117_db_h.index,sha256=JQc2q-oSWwx-sv32muf51tZVgmDl0DgM6m1eJr5Z-V4,379408
|
|
20
|
+
oxymetag/data/pfam_headers_table.txt,sha256=wMg4WvlST6Zi3EzVFudjFHyREqNk8kHDI9Q6th7FdFY,255832
|
|
21
|
+
oxymetag/data/pfam_lengths.tsv,sha256=--0bGxDN2v_WiBo0rKFJMPeOPsOrbaNhyjPAoIF9E5A,366
|
|
22
|
+
oxymetag/scripts/predict_oxygen.R,sha256=NUv23y0l7BQxFUoxHqttlKveh6LYmO2qgP8hciGdpMs,7657
|
|
23
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
oxymetag-1.1.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
25
|
+
oxymetag-1.1.1.dist-info/METADATA,sha256=H6M4nnM-SOojWNgRCEJRMcCiS417gi0iIVvAAUlVryY,11898
|
|
26
|
+
oxymetag-1.1.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
27
|
+
oxymetag-1.1.1.dist-info/entry_points.txt,sha256=-9xMAfrSPtFBEvQWRNVKROTM_3OjEik34mVEsYFwM2k,47
|
|
28
|
+
oxymetag-1.1.1.dist-info/top_level.txt,sha256=G7EHL5Fpxne8CH3w5IDIkrsRmMzaEfOhmTngNovoYi8,15
|
|
29
|
+
oxymetag-1.1.1.dist-info/RECORD,,
|
oxymetag-1.0.0.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
oxymetag/__init__.py,sha256=GM_D7cuLGpBCmwCp3PkT42Q3mnryV3BDvMWjMBqt4L8,441
|
|
2
|
-
oxymetag/cli.py,sha256=ITt3UNUFrdeIskjSaK-705oOcAPUlzq5acqlWCXVDqM,4478
|
|
3
|
-
oxymetag/core.py,sha256=AkuXmhhI2It-QFdfykJcZ-e_6i124yS9RMM-BAH6RlU,8565
|
|
4
|
-
oxymetag/utils.py,sha256=x-WVnR-yNawY13alF_8J9Ihkjpaeg15IlEpZXyn2JSU,2604
|
|
5
|
-
oxymetag/data/.DS_Store,sha256=1lFlJ5EFymdzGAUAaI30vcaaLHt3F1LwpG7xILf9jsM,6148
|
|
6
|
-
oxymetag/data/Oxygen_pfams.csv,sha256=f3_CFPy235BxbX-2Ami8dJpTLQDImM8e_m87QcJvYKo,673
|
|
7
|
-
oxymetag/data/oxygen_model.rds,sha256=8BMWnnIKCALapQnJKLpMnqTymFNAW46E-cQJGU2tJu0,8221
|
|
8
|
-
oxymetag/data/oxymetag_pfams.dmnd,sha256=sSrkriGi-x4Ybf-pQtRRCFh1Wjm4rbQeX9FL9buDbAE,1472431
|
|
9
|
-
oxymetag/data/pfam_headers_table.txt,sha256=wMg4WvlST6Zi3EzVFudjFHyREqNk8kHDI9Q6th7FdFY,255832
|
|
10
|
-
oxymetag/data/pfam_lengths.tsv,sha256=--0bGxDN2v_WiBo0rKFJMPeOPsOrbaNhyjPAoIF9E5A,366
|
|
11
|
-
oxymetag/scripts/predict_oxygen.R,sha256=72Eum7XFtJ-Be5vdIqY8FFIuPnEWTGYDMaZWv5OTPtQ,5549
|
|
12
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
oxymetag-1.0.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
14
|
-
oxymetag-1.0.0.dist-info/METADATA,sha256=eo8h97bwY2mGTgkTNpNjwomUpq6c_k8q4EEsyMiZDIk,8968
|
|
15
|
-
oxymetag-1.0.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
16
|
-
oxymetag-1.0.0.dist-info/entry_points.txt,sha256=-9xMAfrSPtFBEvQWRNVKROTM_3OjEik34mVEsYFwM2k,47
|
|
17
|
-
oxymetag-1.0.0.dist-info/top_level.txt,sha256=G7EHL5Fpxne8CH3w5IDIkrsRmMzaEfOhmTngNovoYi8,15
|
|
18
|
-
oxymetag-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|