Skip to content

Commit

Permalink
plotParallelCoordinates
Browse files Browse the repository at this point in the history
  • Loading branch information
joseph-hellerstein committed May 26, 2024
1 parent ee23667 commit b4a8b1b
Show file tree
Hide file tree
Showing 13 changed files with 900 additions and 960 deletions.
1,313 changes: 375 additions & 938 deletions examples/Controlling-Glucose-Metabolis-in-Yeast.ipynb

Large diffs are not rendered by default.

370 changes: 367 additions & 3 deletions examples/cancer-vitamins.ipynb

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
numpy
build
coverage
colour
control
coverage
docstring-expander
jupyterlab
lmfit
Expand Down
2 changes: 2 additions & 0 deletions src/controlSBML/control_sbml.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def __init__(self, model_reference:str,
output_name=output_name,
is_fixed_input_species=is_fixed_input_species, is_steady_state=is_steady_state) # type: ignore
self._fitter_result = cn.FitterResult()
self.antimony_builder:Optional[AntimonyBuilder] = None # Last antimony builder used

def copy(self):
ctlsb = ControlSBML(self.model_reference,
Expand Down Expand Up @@ -523,6 +524,7 @@ def plotStaircaseResponse(self,
response_ts, builder = self._transfer_function_builder.makeStaircaseResponse(times=times,
staircase=staircase, is_steady_state=self.is_steady_state,
)
self.antimony_builder = builder
self._transfer_function_builder.plotStaircaseResponse(response_ts, **plot_dct)
return StaircaseResponseResult(timeseries=response_ts, antimony_builder=builder)

Expand Down
105 changes: 105 additions & 0 deletions src/controlSBML/plot_parallel_coordinates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Does parallel coordinate plots for a dataframe."""

from colour import Color # type: ignore
from matplotlib import ticker # type: ignore
import numpy as np
import matplotlib.pyplot as plt # type: ignore
import pandas as pd # type: ignore


def plotParallelCoordinates(df, value_column=None, num_category=10,
figsize=(15,5), is_plot=True):
"""
Does a parallel coordinate plots for with lines colored based on categorizing
the value_column.
Args:
df (pd.DataFrame): _description_
value_column (object, optional): Column of dataframe that has the values to
categorize. Defaults to first column.
num_category (int, optional): Number of categories to split the value_column
"""
columns = list(df.columns)
df = df.copy()
df = df.reset_index(drop=False)
# Construct the categories for each row
if value_column is None:
value_column = columns[0]
classify_vals = df[value_column]
value_column_str = str(value_column)
# Create the categories and labels
category_intervals = pd.cut(classify_vals, num_category)
categories = pd.cut(classify_vals, num_category, labels=range(num_category))
category_labels = list(range(num_category))
for idx, category in enumerate(categories):
label = f"{category_intervals[idx].left} to {category_intervals[idx].right}"
category_labels[category] = label
# Create the colors for the categories
red = Color("red")
colors = list(red.range_to(Color("blue"),num_category))
colors.reverse()
# Clean up
for column in ["index"]:
del df[column]
columns = list(df.columns)
x_vals = list(range(len(columns)))
#
# Create sublots along x axis
#
fig, axes = plt.subplots(1, len(columns), sharey=False, figsize=figsize)

# Get min, max and range for each column
# Normalize the data for each column
min_max_range = {}
for column in columns:
min_max_range[column] = [df[column].min(), df[column].max(), np.ptp(df[column])]
df[column] = np.true_divide(df[column] - df[column].min(), np.ptp(df[column]))

# Plot each row
for i, ax in enumerate(axes):
for idx in df.index:
category = categories[idx]
ax.plot(x_vals, df.loc[idx, columns], c=str(colors[category]))
if i < len(x_vals)-1:
ax.set_xlim([x_vals[i], x_vals[i+1]])

# Set the tick positions and labels on y axis for each plot
# Tick positions based on normalised data
# Tick labels are based on original data
def set_ticks_for_axis(dim, ax, ticks):
min_val, max_val, val_range = min_max_range[columns[dim]]
step = val_range / float(ticks-1)
tick_labels = [round(min_val + step * i, 2) for i in range(ticks)]
norm_min = df[columns[dim]].min()
norm_range = np.ptp(df[columns[dim]])
norm_step = norm_range / float(ticks-1)
ticks = [round(norm_min + norm_step * i, 2) for i in range(ticks)]
ax.yaxis.set_ticks(ticks)
ax.set_yticklabels(tick_labels)

for dim, ax in enumerate(axes):
ax.xaxis.set_major_locator(ticker.FixedLocator([dim]))
set_ticks_for_axis(dim, ax, ticks=6)
ax.set_xticklabels([columns[dim]])

# Move the final axis' ticks to the right-hand side
ax = plt.twinx(axes[-2])
dim = len(axes) - 1
ax.xaxis.set_major_locator(ticker.FixedLocator([x_vals[-2], x_vals[-1]]))
set_ticks_for_axis(dim, ax, ticks=6)
ax.set_xticklabels([columns[-2], columns[-1]])

# Remove space between subplots
axes[0].remove()
plt.subplots_adjust(wspace=0)
axes[-1].remove()

# Add legend to plot
plt.legend(
[plt.Line2D((0,1),(0,0), color=str(colors[v])) for v in range(num_category)],
category_labels,
bbox_to_anchor=(1.2, 1), loc=2, borderaxespad=0.)

plt.title(f"Values by {value_column_str} category")
if is_plot:
plt.show()
2 changes: 1 addition & 1 deletion src/controlSBML/sbml_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from controlSBML import util
from controlSBML.option_management.option_manager import OptionManager

import matplotlib.pyplot as plt
import matplotlib.pyplot as plt # type: ignore
import pandas as pd # type: ignore
import numpy as np # type: ignore
import tellurium as te # type: ignore
Expand Down
3 changes: 1 addition & 2 deletions src/controlSBML/siso_closed_loop_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@

import collections
import control # type: ignore
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt # type: ignore
import numpy as np
import os
import pandas as pd # type: ignore
import seaborn as sns # type: ignore

Expand Down
1 change: 1 addition & 0 deletions src/controlSBML/siso_transfer_function_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def makeStaircaseResponse(self, staircase=Staircase(), mgr=None, times=None,
final_value=staircase.final_value, is_steady_state=is_steady_state,
inplace=False)
if result_ts is None:
import pdb; pdb.set_trace()
raise ValueError("Unable to simulate staircase response")
staircase.setNumPoint(len(result_ts))
staircase_name = "%s_%s" % (self.input_name, STAIRCASE)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_control_sbml.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import controlSBML.util as util # type: ignore

import control # type: ignore
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt # type: ignore
import numpy as np
import os
from typing import List
Expand Down Expand Up @@ -496,5 +496,6 @@ def testBug15(self):




if __name__ == '__main__':
unittest.main()
35 changes: 35 additions & 0 deletions tests/test_plot_parallel_coordinates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from controlSBML.plot_parallel_coordinates import plotParallelCoordinates # type: ignore

import pandas as pd # type: ignore
import numpy as np
import unittest


IGNORE_TEST = False
IS_PLOT = False
# Create data
columns = ['mpg', 'displacement', 'cylinders', 'horsepower', 'weight', 'acceleration']
dct = {}
for column in columns:
dct[column] = np.random.rand(10)
DF = 10*pd.DataFrame(dct)


#############################
# Tests
#############################
class TestFunction(unittest.TestCase):

def setUp(self):
self.df = DF.copy()

def testPlotParallelCoordinates(self):
if IGNORE_TEST:
return
plotParallelCoordinates(self.df, value_column='mpg', num_category=3,
is_plot=IS_PLOT)



if __name__ == '__main__':
unittest.main()
12 changes: 6 additions & 6 deletions tests/test_siso_maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def setUp(self):
self.maker = SISOMaker(LINEAR_MDL, model_id="linear")

def testConstructor(self):
#if IGNORE_TEST:
# return
if IGNORE_TEST:
return
self.assertTrue("RoadRunner" in str(type(self.maker.roadrunner)))
self.assertEqual(self.maker.input_name, "S1")
self.assertEqual(self.maker.output_name, "S2")
Expand All @@ -54,13 +54,13 @@ def testMakeClosedLoop(self):
self.maker.makeClosedLoop(is_plot=IS_PLOT)

def testRunModel(self):
#if IGNORE_TEST:
# return
if IGNORE_TEST:
return
self.maker.runModel(LINEAR_MDL, is_plot=IS_PLOT)

def testRunBiomodels(self):
#if IGNORE_TEST:
# return
if IGNORE_TEST:
return
self.maker.runBiomodels(start=0, end=5, is_report=True, end_time=20)


Expand Down
4 changes: 2 additions & 2 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
IS_PLOT = False
SIZE = 10
if IS_PLOT:
import matplotlib
import matplotlib # type: ignore
matplotlib.use('TkAgg')
times = [1.0*n for n in range(SIZE)]
TS = Timeseries(pd.DataFrame({"a": range(SIZE)}), times=times)
Expand Down Expand Up @@ -301,4 +301,4 @@ def test(value, digits, expected):


if __name__ == '__main__':
unittest.main()
unittest.main()
7 changes: 1 addition & 6 deletions todo.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
Resolve rateOf
Use filter?
Update docs on REAMDE
Create pypi version
CTLSB.plotSBMLSystem - does a plot with multiple inputs and multiple outputs.
Noise & disturbances
Keep last antimony builder in designs (reconstruct for best design?)
MISO
Bugs
"Stoichiometry matrix does not exist for this model" -- 379
Expand Down

0 comments on commit b4a8b1b

Please sign in to comment.