From 7a161c03bd7f3dfcca8040318c9d3e2bd9c0f283 Mon Sep 17 00:00:00 2001 From: boilpy Date: Sun, 18 Sep 2022 23:07:58 -0500 Subject: [PATCH 1/8] feat: add to_dataframe to dictlist --- src/cobra/core/dictlist.py | 27 +++++++++++++++++++++++++++ src/cobra/core/formula.py | 2 ++ src/cobra/core/gene.py | 2 ++ src/cobra/core/group.py | 2 ++ src/cobra/core/metabolite.py | 2 ++ src/cobra/core/reaction.py | 2 ++ src/cobra/core/species.py | 4 +++- 7 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/cobra/core/dictlist.py b/src/cobra/core/dictlist.py index dad2c71b4..ef59519d4 100644 --- a/src/cobra/core/dictlist.py +++ b/src/cobra/core/dictlist.py @@ -16,6 +16,7 @@ ) import numpy as np +import pandas as pd from .object import Object @@ -542,3 +543,29 @@ def __dir__(self) -> list: attributes.append("_dict") attributes.extend(self._dict.keys()) return attributes + + def to_dataframe(self): + """Convert to a pandas dataframe.""" + + item = None + columns = [] + + if self: + item = self[0] + columns = [col for col in item._DF_ATTRS if col != "id"] + + data = [] + ids = [] + + for item in self: + ids.append(item.id) + data.append([getattr(item, attr) for attr in columns]) + + df = pd.DataFrame(columns = columns, data = data, index = ids) + + return df + + def _repr_html_(self): + """Display as HTML.""" + df = self.to_dataframe() + return df._repr_html_() \ No newline at end of file diff --git a/src/cobra/core/formula.py b/src/cobra/core/formula.py index 8aeab23bf..fbbb33d58 100644 --- a/src/cobra/core/formula.py +++ b/src/cobra/core/formula.py @@ -22,6 +22,8 @@ class Formula(Object): A legal formula string contains only letters and numbers. """ + _DF_ATTRS = ["formula"] + def __init__(self, formula: Optional[str] = None, **kwargs) -> None: """Initialize a formula. diff --git a/src/cobra/core/gene.py b/src/cobra/core/gene.py index 4d8a13576..4b812c4aa 100644 --- a/src/cobra/core/gene.py +++ b/src/cobra/core/gene.py @@ -204,6 +204,8 @@ class Gene(Species): used. """ + _DF_ATTRS = Species._DF_ATTRS + ["gene_reaction_rule"] + # noinspection PyShadowingBuiltins def __init__(self, id: str = None, name: str = "", functional: bool = True) -> None: """Initialize a gene. diff --git a/src/cobra/core/group.py b/src/cobra/core/group.py index ac2e508b3..39651ff49 100644 --- a/src/cobra/core/group.py +++ b/src/cobra/core/group.py @@ -38,6 +38,8 @@ class Group(Object): or member is involved in a disease phenotype). """ + _DF_ATTRS = ["id", "name", "kind"] + KIND_TYPES = ("collection", "classification", "partonomy") def __init__( diff --git a/src/cobra/core/metabolite.py b/src/cobra/core/metabolite.py index 6b3d31dc9..131a94e57 100644 --- a/src/cobra/core/metabolite.py +++ b/src/cobra/core/metabolite.py @@ -44,6 +44,8 @@ class Metabolite(Species): Compartment of the metabolite. """ + _DF_ATTRS = Species._DF_ATTRS + ["formula", "compartment", "charge"] + # noinspection PyShadowingBuiltins def __init__( self, diff --git a/src/cobra/core/reaction.py b/src/cobra/core/reaction.py index 0623ae170..f4f0ac23b 100644 --- a/src/cobra/core/reaction.py +++ b/src/cobra/core/reaction.py @@ -84,6 +84,8 @@ class Reaction(Object): **kwargs: Further keyword arguments are passed on to the parent class. """ + + _DF_ATTRS = ["id", "name", "subsystem", "gene_reaction_rule", "lower_bound", "upper_bound"] # noinspection PyShadowingBuiltins def __init__( diff --git a/src/cobra/core/species.py b/src/cobra/core/species.py index 39749fcd0..13dd015b6 100644 --- a/src/cobra/core/species.py +++ b/src/cobra/core/species.py @@ -25,6 +25,8 @@ class Species(Object): A human readable name. """ + _DF_ATTRS = ["id", "name"] + # noinspection PyShadowingBuiltins def __init__( self, id: Optional[str] = None, name: Optional[str] = None, **kwargs @@ -100,4 +102,4 @@ def model(self) -> Optional["Model"]: Returns the cobra model that the species is associated with. None if there is no model associated with this species. """ - return self._model + return self._model \ No newline at end of file From c4d8f3289255ff1300a54f60cc3365766d22e94e Mon Sep 17 00:00:00 2001 From: boilpy Date: Sun, 18 Sep 2022 23:09:02 -0500 Subject: [PATCH 2/8] fix: fix gene df attrs --- src/cobra/core/gene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cobra/core/gene.py b/src/cobra/core/gene.py index 4b812c4aa..e6e1a1543 100644 --- a/src/cobra/core/gene.py +++ b/src/cobra/core/gene.py @@ -204,7 +204,7 @@ class Gene(Species): used. """ - _DF_ATTRS = Species._DF_ATTRS + ["gene_reaction_rule"] + _DF_ATTRS = Species._DF_ATTRS # noinspection PyShadowingBuiltins def __init__(self, id: str = None, name: str = "", functional: bool = True) -> None: From 28376e60ae68c57961dea0b7c357a90342e599e7 Mon Sep 17 00:00:00 2001 From: boilpy Date: Mon, 19 Sep 2022 15:02:22 -0500 Subject: [PATCH 3/8] fix: fix linting issue --- src/cobra/core/dictlist.py | 9 ++++----- src/cobra/core/reaction.py | 11 +++++++++-- src/cobra/core/species.py | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/cobra/core/dictlist.py b/src/cobra/core/dictlist.py index ef59519d4..32d45ea85 100644 --- a/src/cobra/core/dictlist.py +++ b/src/cobra/core/dictlist.py @@ -544,9 +544,8 @@ def __dir__(self) -> list: attributes.extend(self._dict.keys()) return attributes - def to_dataframe(self): + def to_df(self): """Convert to a pandas dataframe.""" - item = None columns = [] @@ -561,11 +560,11 @@ def to_dataframe(self): ids.append(item.id) data.append([getattr(item, attr) for attr in columns]) - df = pd.DataFrame(columns = columns, data = data, index = ids) + df = pd.DataFrame(columns=columns, data=data, index=ids) return df def _repr_html_(self): """Display as HTML.""" - df = self.to_dataframe() - return df._repr_html_() \ No newline at end of file + df = self.to_df() + return df._repr_html_() diff --git a/src/cobra/core/reaction.py b/src/cobra/core/reaction.py index f4f0ac23b..4508096ec 100644 --- a/src/cobra/core/reaction.py +++ b/src/cobra/core/reaction.py @@ -84,8 +84,15 @@ class Reaction(Object): **kwargs: Further keyword arguments are passed on to the parent class. """ - - _DF_ATTRS = ["id", "name", "subsystem", "gene_reaction_rule", "lower_bound", "upper_bound"] + + _DF_ATTRS = [ + "id", + "name", + "subsystem", + "gene_reaction_rule", + "lower_bound", + "upper_bound", + ] # noinspection PyShadowingBuiltins def __init__( diff --git a/src/cobra/core/species.py b/src/cobra/core/species.py index 13dd015b6..413c038ec 100644 --- a/src/cobra/core/species.py +++ b/src/cobra/core/species.py @@ -102,4 +102,4 @@ def model(self) -> Optional["Model"]: Returns the cobra model that the species is associated with. None if there is no model associated with this species. """ - return self._model \ No newline at end of file + return self._model From d885b6303da4405cc4808a48392a992d1a36ff03 Mon Sep 17 00:00:00 2001 From: boilpy Date: Mon, 19 Sep 2022 15:06:37 -0500 Subject: [PATCH 4/8] test: add test cases for dict list --- tests/test_core/test_dictlist.py | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_core/test_dictlist.py b/tests/test_core/test_dictlist.py index 5b2d35586..3ec28bf07 100644 --- a/tests/test_core/test_dictlist.py +++ b/tests/test_core/test_dictlist.py @@ -6,6 +6,7 @@ from typing import Tuple import pytest +import pandas as pd from cobra.core import DictList, Object @@ -468,3 +469,38 @@ def test_union(dict_list: Tuple[Object, DictList]) -> None: # Add only 1 element assert len(test_list) == 2 assert test_list.index("test2") == 1 + +def test_to_df(dict_list: Tuple[Object, DictList]) -> None: + """Test to_df for dictlist. + + Parameters + ---------- + dict_list : tuple + The fixture for filled dictlist. + + """ + _, test_list = dict_list + test_list.name = "foo" + df = test_list.to_df() + assert isinstance(df, pd.DataFrame) + assert len(df) == 1 + assert "id" in df.columns + assert "test1" in df["id"].values + assert "name" in df.columns + assert "foo" in df["name"].values + +def test__repr_html(dict_list: Tuple[Object, DictList]) -> None: + """Test _repr_html_ for dictlist. + + Parameters + ---------- + dict_list : tuple + The fixture for filled dictlist. + + """ + _, test_list = dict_list + test_list.name = "foo" + html = test_list._repr_html_() + assert isinstance(html, str) + assert "test1" in html + assert "foo" in html \ No newline at end of file From 5b45b1f8e75c08af40ea9f9e6b233cfa788a98c7 Mon Sep 17 00:00:00 2001 From: boilpy Date: Mon, 19 Sep 2022 15:08:14 -0500 Subject: [PATCH 5/8] test: add test cases for dict list --- tests/test_core/test_dictlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core/test_dictlist.py b/tests/test_core/test_dictlist.py index 3ec28bf07..b5630f542 100644 --- a/tests/test_core/test_dictlist.py +++ b/tests/test_core/test_dictlist.py @@ -5,8 +5,8 @@ from pickle import HIGHEST_PROTOCOL, dumps, loads from typing import Tuple -import pytest import pandas as pd +import pytest from cobra.core import DictList, Object From 4353ecd4eb3f3c6be68e9b2a4843f9a8757bf82d Mon Sep 17 00:00:00 2001 From: boilpy Date: Mon, 19 Sep 2022 23:38:25 -0500 Subject: [PATCH 6/8] feat: update for object --- setup.cfg | 1 + src/cobra/core/object.py | 1 + src/cobra/core/reaction.py | 4 +--- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index d8268a0e3..b1156db63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,6 +68,7 @@ install_requires = rich >=8.0 ruamel.yaml ~=0.16 swiglpk + tqdm ~=4.0 tests_require = tox packages = find: diff --git a/src/cobra/core/object.py b/src/cobra/core/object.py index 78d092d05..d2bf38d8e 100644 --- a/src/cobra/core/object.py +++ b/src/cobra/core/object.py @@ -5,6 +5,7 @@ class Object: """Defines common behavior of object in cobra.core.""" + _DF_ATTRS = ["id", "name", "notes", "annotation"] def __init__(self, id: Optional[str] = None, name: str = "") -> None: """Initialize a simple object with an identifier. diff --git a/src/cobra/core/reaction.py b/src/cobra/core/reaction.py index 4508096ec..7d061fb9a 100644 --- a/src/cobra/core/reaction.py +++ b/src/cobra/core/reaction.py @@ -85,9 +85,7 @@ class Reaction(Object): Further keyword arguments are passed on to the parent class. """ - _DF_ATTRS = [ - "id", - "name", + _DF_ATTRS = Object._DF_ATTRS + [ "subsystem", "gene_reaction_rule", "lower_bound", From e631c8e84ecf7504c3fa6c6c09dcc7d7e9128ce5 Mon Sep 17 00:00:00 2001 From: boilpy Date: Mon, 19 Sep 2022 23:38:54 -0500 Subject: [PATCH 7/8] feat: add progress for fva --- src/cobra/flux_analysis/variability.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/cobra/flux_analysis/variability.py b/src/cobra/flux_analysis/variability.py index 4b1066f2b..a367c5ba8 100644 --- a/src/cobra/flux_analysis/variability.py +++ b/src/cobra/flux_analysis/variability.py @@ -17,6 +17,9 @@ from .loopless import loopless_fva_iter from .parsimonious import add_pfba +# from tqdm.auto import tqdm +from rich.progress import track + if TYPE_CHECKING: from cobra import Gene, Model, Reaction @@ -236,18 +239,28 @@ def flux_variability_analysis( # objective direction for all reactions. This creates a # slight overhead but seems the most clean. chunk_size = len(reaction_ids) // processes + description = "Performing FVA for %s fluxes" % what with ProcessPool( processes, initializer=_init_worker, initargs=(model, loopless, what[:3]), ) as pool: - for rxn_id, value in pool.imap_unordered( - _fva_step, reaction_ids, chunksize=chunk_size + for rxn_id, value in track( + pool.imap_unordered( + _fva_step, reaction_ids, chunksize=chunk_size + ), + total=num_reactions, + description=description ): fva_result.at[rxn_id, what] = value else: _init_worker(model, loopless, what[:3]) - for rxn_id, value in map(_fva_step, reaction_ids): + for rxn_id, value in track( + map(_fva_step, reaction_ids), + total=num_reactions, + description=description + ): + fva_result.at[rxn_id, what] = value fva_result.at[rxn_id, what] = value return fva_result[["minimum", "maximum"]] From 4a9df0dedf1c556fd160c1904ff434d5addce8da Mon Sep 17 00:00:00 2001 From: boilpy Date: Mon, 19 Sep 2022 23:44:54 -0500 Subject: [PATCH 8/8] feat: add progress to fva --- release-notes/next-release.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes/next-release.md b/release-notes/next-release.md index 8bb9bbe10..a61842cff 100644 --- a/release-notes/next-release.md +++ b/release-notes/next-release.md @@ -3,6 +3,7 @@ ## New features * View number of genes in model notebook representation. +* Add progress bar to `flux_variability_analysis`. ## Fixes