Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add methods to export results in tabular format #280

Merged
merged 45 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6b8d3d3
add print links method to LinkGraph, improve LinkGraph string represe…
liannette Oct 15, 2024
cdd26c3
feat: add a method to print tabular results files
liannette Oct 16, 2024
ec8b8ae
improve method names and docstrings, remove unused method to export g…
liannette Oct 16, 2024
2207df1
improve doctring and typing
liannette Oct 16, 2024
c6e166a
fix a failing test
liannette Oct 16, 2024
32ca3dd
refactor a little bit the spectrum method to covert to dict
liannette Oct 16, 2024
8e7945d
change the output format for gnps_annotations in metabolomics results…
liannette Oct 16, 2024
2592810
fix: convert int to str before using join
liannette Oct 17, 2024
7f53de8
change representation of empty values in output files for improved in…
liannette Oct 17, 2024
ad049c8
refactoring the export methods
liannette Oct 17, 2024
b220fb0
small refactor: specify staticmethod
liannette Oct 18, 2024
f98fa98
add more tests
liannette Oct 18, 2024
a8a8329
correct typing in doctrings
liannette Oct 18, 2024
c6c33e6
typing: changed typings to pass mypy static typing checks
liannette Oct 22, 2024
a260338
refactor: change the order of methods/functions
liannette Oct 22, 2024
3289683
restore the order of already existing functions and methods
liannette Nov 4, 2024
d2272e2
make dicts json compatible
liannette Nov 4, 2024
cb49209
rename functions and variables
liannette Nov 4, 2024
6a4da5f
refactor: changed the place when the index is added to the link dict
liannette Nov 4, 2024
edcc7db
use csv package to write the tabular output files
liannette Nov 4, 2024
05f9f76
make sure all elements of the input list have the same type of data.
liannette Nov 4, 2024
bff7731
shorten to long doc string lines, correct some doc strings
liannette Nov 4, 2024
d4bf9fb
tests: adapted the test to the changes
liannette Nov 4, 2024
2c05efb
remove a file that was committed by accident
liannette Nov 4, 2024
229a11d
Merge branch 'NPLinker:dev' into output_files
liannette Nov 5, 2024
32d78c3
Improve docstrings
liannette Nov 19, 2024
b04226b
Improve docstrings
liannette Nov 19, 2024
5fd4108
refactor: add method to convert a value to string for tabular output
liannette Nov 19, 2024
8137f7d
Merge branch 'output_files' of https://github.com/liannette/nplinker …
liannette Nov 19, 2024
940eb19
improve doctring, add a comment about key order of bgc dict represent…
liannette Nov 20, 2024
e551dcc
move to_string method to the BGC/Spectrum class, add a to_tabular method
liannette Nov 20, 2024
f9ae9f2
add tests for the to_string method
liannette Nov 20, 2024
1b00262
change to_tabular to it returns a list and not a string
liannette Nov 20, 2024
0d6bec3
refactor: to_tabular returns dict, to_string turned into private func…
liannette Nov 25, 2024
41757c7
fix typing in to_tabular methods
liannette Dec 2, 2024
b94eddf
update docstrings and comments
liannette Dec 2, 2024
94bcb67
ensure 0 and 0.0 are correctly converted to strings, and not to empty…
liannette Dec 2, 2024
16a56c7
change the order of methods
liannette Dec 2, 2024
183bd5f
remove whitespace in blank lines
liannette Dec 2, 2024
e2227df
update and add tests
liannette Dec 2, 2024
642c67c
change variable name to fix mypy error
liannette Dec 2, 2024
7cd675f
test: trying to fix unit test issue where the spectrum rt is a dict i…
liannette Dec 2, 2024
cacd504
Merge branch 'NPLinker:dev' into output_files
liannette Dec 2, 2024
19b6f1e
tests: add precursor charge to the test spectra
liannette Dec 2, 2024
40391fe
Update src/nplinker/metabolomics/spectrum.py
CunliangGeng Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/format-typing-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Install ruff and mypy
run: |
pip install ruff mypy typing_extensions \
types-Deprecated types-beautifulsoup4 types-jsonschema types-networkx pandas-stubs
types-Deprecated types-beautifulsoup4 types-jsonschema types-networkx types-tabulate pandas-stubs
- name: Get all changed python files
id: changed-python-files
uses: tj-actions/changed-files@v44
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dev = [
"types-beautifulsoup4",
"types-jsonschema",
"types-networkx",
"types-tabulate",
"pandas-stubs",
# docs
"black",
Expand Down
55 changes: 43 additions & 12 deletions src/nplinker/genomics/bgc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from typing import Any
from deprecated import deprecated
from nplinker.strain import Strain
from .aa_pred import predict_aa
Expand Down Expand Up @@ -116,18 +117,6 @@ def __reduce__(self) -> tuple:
"""Reduce function for pickling."""
return (self.__class__, (self.id, *self.product_prediction), self.__dict__)

def add_parent(self, gcf: GCF) -> None:
"""Add a parent GCF to the BGC.

Args:
gcf: gene cluster family
"""
gcf.add_bgc(self)

def detach_parent(self, gcf: GCF) -> None:
"""Remove a parent GCF."""
gcf.detach_bgc(self)

@property
def strain(self) -> Strain | None:
"""Get the strain of the BGC."""
Expand Down Expand Up @@ -161,6 +150,18 @@ def bigscape_classes(self) -> set[str | None]:
"""
return {p.bigscape_class for p in self.parents}

def add_parent(self, gcf: GCF) -> None:
"""Add a parent GCF to the BGC.

Args:
gcf: gene cluster family
"""
gcf.add_bgc(self)

def detach_parent(self, gcf: GCF) -> None:
"""Remove a parent GCF."""
gcf.detach_bgc(self)

def is_mibig(self) -> bool:
"""Check if the BGC is a MIBiG reference BGC or not.

Expand All @@ -173,6 +174,36 @@ def is_mibig(self) -> bool:
"""
return self.id.startswith("BGC")

def to_dict(self) -> dict[str, Any]:
liannette marked this conversation as resolved.
Show resolved Hide resolved
"""Convert the BGC object to a dictionary for exporting results.
liannette marked this conversation as resolved.
Show resolved Hide resolved

This method compiles relevant information from the BGC object and formats it into a dictionary.
Each key-value pair in the dictionary represents a specific attribute of the BGC.

Returns:
A dictionary containing the following key-value pairs:
liannette marked this conversation as resolved.
Show resolved Hide resolved
- GCF_id (set): A set of GCF IDs.
- GCF_bigscape_class (set): A set of BiG-SCAPE classes.
- strain_id (str | None): The ID of the strain.
- description (str | None): A description of the BGC.
- BGC_name (str): The name of the BGC.
- product_prediction (tuple): (predicted) natural products or product classes of the BGC.
- mibig_bgc_class (tuple[str] | None): MIBiG biosynthetic classes to which the BGC belongs.
- antismash_id (str | None): The antiSMASH ID.
- antismash_region (int | None): The antiSMASH region.
"""
return {
"GCF_id": {gcf.id for gcf in self.parents if gcf.id is not None},
"GCF_bigscape_class": {bsc for bsc in self.bigscape_classes if bsc is not None},
"strain_id": self.strain.id if self.strain is not None else None,
"description": self.description,
"BGC_name": self.id,
"product_prediction": self.product_prediction,
"mibig_bgc_class": self.mibig_bgc_class,
"antismash_id": self.antismash_id,
"antismash_region": self.antismash_region,
}
liannette marked this conversation as resolved.
Show resolved Hide resolved

# CG: why not providing whole product but only amino acid as product monomer?
# this property is not used in NPLinker core business.
@property
Expand Down
27 changes: 27 additions & 0 deletions src/nplinker/metabolomics/spectrum.py
liannette marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations
from functools import cached_property
from typing import TYPE_CHECKING
from typing import Any
import numpy as np
from nplinker.strain import Strain
from nplinker.strain import StrainCollection
Expand Down Expand Up @@ -97,3 +98,29 @@ def has_strain(self, strain: Strain) -> bool:
True when the given strain exist in the spectrum.
"""
return strain in self.strains

def to_dict(self) -> dict[str, Any]:
"""Convert the Spectrum object to a dictionary for exporting results.
liannette marked this conversation as resolved.
Show resolved Hide resolved

This method compiles relevant information from the Spectrum object into a dictionary format.
Each key-value pair in the dictionary represents a specific attribute of the Spectrum Object.

liannette marked this conversation as resolved.
Show resolved Hide resolved
Returns:
A dictionary containing containing the following key-value pairs:
liannette marked this conversation as resolved.
Show resolved Hide resolved
- "spectrum_id" (str): The unique identifier of the spectrum.
- "num_strains_with_spectrum" (int): The number of strains associated with the spectrum.
- "precursor_mz" (float): The precursor m/z value, rounded to four decimal places.
- "rt" (float): The retention time, rounded to three decimal places.
- "molecular_family" (str | None ): The identifier of the molecular family.
- "gnps_id" (str | None ): The GNPS identifier.
- "gnps_annotations" (dict): A dictionary of GNPS annotations.
"""
return {
"spectrum_id": self.id,
"num_strains_with_spectrum": len(self.strains),
"precursor_mz": round(self.precursor_mz, 4),
"rt": round(self.rt, 3),
"molecular_family": self.family.id if self.family else None,
"gnps_id": self.gnps_id,
"gnps_annotations": self.gnps_annotations,
}
95 changes: 70 additions & 25 deletions src/nplinker/nplinker.py
liannette marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -168,34 +168,50 @@ def scoring_methods(self) -> list[str]:
"""Get names of all valid scoring methods."""
return list(self._valid_scoring_methods.keys())

def load_data(self):
"""Load all data from files into memory.

This method is a convenience function that calls the
[`DatasetArranger`][nplinker.arranger.DatasetArranger] class to arrange data files
(download, generate and/or validate data) in the [correct directory structure][working-directory-structure],
and then calls the [`DatasetLoader`][nplinker.loader.DatasetLoader] class to load all data
from the files into memory.
def export_objects(self, objects: Sequence[BGC | Spectrum], filename: str) -> None:
liannette marked this conversation as resolved.
Show resolved Hide resolved
"""Exports the data for a list of BGC or Spectrum objects to a specified file in tab-separated format.

The loaded data is stored in various data containers for easy access, e.g.
[`self.bgcs`][nplinker.NPLinker.bgcs] for all BGC objects,
[`self.strains`][nplinker.NPLinker.strains] for all Strain objects, etc.
Args:
objects (list[BGC | Spectrum]): A list of BGC or Spectrum objects to be exported.
liannette marked this conversation as resolved.
Show resolved Hide resolved
filename (str): The name of the file where the data will be saved.
"""
arranger = DatasetArranger(self.config)
arranger.arrange()
loader = DatasetLoader(self.config)
loader.load()
headers = objects[0].to_dict().keys()
with open(self._output_dir / filename, "w") as f:
f.write("\t".join(headers) + "\n")
liannette marked this conversation as resolved.
Show resolved Hide resolved
for obj in objects:
row_data = obj.to_dict()
formatted_row = []
for header in headers:
item = row_data.get(header, "")
# Convert list, tuple, set to comma-separated string
if isinstance(item, (list, tuple, set)):
formatted_row.append(", ".join(map(str, item)))
# Convert dict to comma-separated string
elif isinstance(item, dict):
formatted_row.append(", ".join([f"{k}:{v}" for k, v in item.items()]))
# Convert non-empty value to string
elif item:
formatted_row.append(str(item))
# Convert empty value to empty string
else:
formatted_row.append("")
f.write("\t".join(formatted_row) + "\n")

def export_results(self, lg: LinkGraph | None = None) -> None:
liannette marked this conversation as resolved.
Show resolved Hide resolved
"""Exports the results to the output directory in tab-separated format.

This method exports genomics and metabolomics data to their respective
TSV files in the specified output directory. If a LinkGraph object is
provided, it also exports the links data to a TSV file.

self._bgc_dict = {bgc.id: bgc for bgc in loader.bgcs}
self._gcf_dict = {gcf.id: gcf for gcf in loader.gcfs}
self._spec_dict = {spec.id: spec for spec in loader.spectra}
self._mf_dict = {mf.id: mf for mf in loader.mfs}

self._mibig_bgcs = loader.mibig_bgcs
self._strains = loader.strains
self._product_types = loader.product_types
self._chem_classes = loader.chem_classes
self._class_matches = loader.class_matches
Args:
lg (LinkGraph | None): An optional LinkGraph object. If provided,
the links data will be exported to 'links.tsv'.
"""
self.export_objects(self.bgcs, "genomics_data.tsv")
self.export_objects(self.spectra, "metabolomics_data.tsv")
if lg is not None:
lg.export_links(self._output_dir / "links.tsv")

@overload
def get_links(
Expand Down Expand Up @@ -281,6 +297,35 @@ def get_links(

return scoring.get_links(*objects, **scoring_params)

def load_data(self):
"""Load all data from files into memory.

This method is a convenience function that calls the
[`DatasetArranger`][nplinker.arranger.DatasetArranger] class to arrange data files
(download, generate and/or validate data) in the [correct directory structure][working-directory-structure],
and then calls the [`DatasetLoader`][nplinker.loader.DatasetLoader] class to load all data
from the files into memory.

The loaded data is stored in various data containers for easy access, e.g.
[`self.bgcs`][nplinker.NPLinker.bgcs] for all BGC objects,
[`self.strains`][nplinker.NPLinker.strains] for all Strain objects, etc.
"""
arranger = DatasetArranger(self.config)
arranger.arrange()
loader = DatasetLoader(self.config)
loader.load()

self._bgc_dict = {bgc.id: bgc for bgc in loader.bgcs}
self._gcf_dict = {gcf.id: gcf for gcf in loader.gcfs}
self._spec_dict = {spec.id: spec for spec in loader.spectra}
self._mf_dict = {mf.id: mf for mf in loader.mfs}

self._mibig_bgcs = loader.mibig_bgcs
self._strains = loader.strains
self._product_types = loader.product_types
self._chem_classes = loader.chem_classes
self._class_matches = loader.class_matches

def lookup_bgc(self, id: str) -> BGC | None:
"""Get the BGC object with the given ID.

Expand Down
Loading
Loading