Skip to content

Commit 0eb5a3d

Browse files
Merge pull request #61 from savitakartik/nodes_tab
First pass at Nodes tab
2 parents 953c8e6 + e289257 commit 0eb5a3d

File tree

14 files changed

+275
-316
lines changed

14 files changed

+275
-316
lines changed

app.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import sys
21
import logging
2+
import pathlib
3+
import sys
34

4-
import numpy as np
55
import panel as pn
6-
import hvplot.pandas
7-
import holoviews as hv
8-
import pandas as pd
9-
106
import tskit
11-
import utils
127

13-
import pathlib
14-
import functools
158
import model
169
import pages
1710

@@ -22,7 +15,6 @@
2215
path = pathlib.Path(sys.argv[1])
2316
tsm = model.TSModel(tskit.load(path), path.name)
2417

25-
2618
pn.extension(sizing_mode="stretch_width")
2719
pn.extension("tabulator")
2820

@@ -32,6 +24,7 @@
3224
"Edges": pages.edges,
3325
"Edge Explorer": pages.edge_explorer,
3426
"Trees": pages.trees,
27+
"Nodes": pages.nodes,
3528
}
3629

3730

config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Global plot settings
22
PLOT_WIDTH = 1000
33
PLOT_HEIGHT = 600
4-
THRESHOLD = 1000 # max number of points to overlay on a plot
4+
THRESHOLD = 1000 # max number of points to overlay on a plot

model.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from functools import cached_property
22

3-
import tskit
4-
import numpy as np
53
import numba
4+
import numpy as np
65
import pandas as pd
7-
import numba
6+
import tskit
87

98
spec = [
109
("num_edges", numba.int64),
@@ -44,7 +43,7 @@ def __init__(
4443
self.in_range = np.zeros(2, dtype=np.int64)
4544
self.out_range = np.zeros(2, dtype=np.int64)
4645

47-
def next(self):
46+
def next(self): # noqa
4847
left = self.interval[1]
4948
j = self.in_range[1]
5049
k = self.out_range[1]
@@ -221,7 +220,7 @@ def mutations_df(self):
221220
unknown = tskit.is_unknown_time(mutations_time)
222221
mutations_time[unknown] = self.ts.nodes_time[mutations_node[unknown]]
223222

224-
node_flag = ts.nodes_flags[mutations_node]
223+
# node_flag = ts.nodes_flags[mutations_node]
225224
position = ts.sites_position[ts.mutations_site]
226225

227226
tables = self.ts.tables
@@ -341,18 +340,22 @@ def nodes_df(self):
341340
child_left, child_right = self.child_bounds(
342341
ts.num_nodes, ts.edges_left, ts.edges_right, ts.edges_child
343342
)
343+
is_sample = np.zeros(ts.num_nodes)
344+
is_sample[ts.samples()] = 1
344345
df = pd.DataFrame(
345346
{
346347
"time": ts.nodes_time,
347348
"num_mutations": self.nodes_num_mutations,
348349
"ancestors_span": child_right - child_left,
350+
"is_sample": is_sample,
349351
}
350352
)
351353
return df.astype(
352354
{
353355
"time": "float64",
354356
"num_mutations": "int",
355357
"ancestors_span": "float64",
358+
"is_sample": "bool",
356359
}
357360
)
358361

@@ -437,6 +440,23 @@ def make_sliding_windows(self, iterable, size, overlap=0):
437440
end += step
438441
yield iterable[start:]
439442

443+
def calc_mean_node_arity(self):
444+
span_sums = np.bincount(
445+
self.ts.edges_parent,
446+
weights=self.ts.edges_right - self.ts.edges_left,
447+
minlength=self.ts.num_nodes,
448+
)
449+
node_spans = self.ts.sample_count_stat(
450+
[self.ts.samples()],
451+
lambda x: (x > 0),
452+
1,
453+
polarised=True,
454+
span_normalise=False,
455+
strict=False,
456+
mode="node",
457+
)[:, 0]
458+
return span_sums / node_spans
459+
440460
def calc_site_tree_index(self):
441461
return (
442462
np.searchsorted(
@@ -459,20 +479,3 @@ def calc_mutations_per_tree(self):
459479
mutations_per_tree = np.zeros(self.ts.num_trees, dtype=np.int64)
460480
mutations_per_tree[unique_values] = counts
461481
return mutations_per_tree
462-
463-
def calc_mean_node_arity(self):
464-
span_sums = np.bincount(
465-
self.ts.edges_parent,
466-
weights=self.ts.edges_right - self.ts.edges_left,
467-
minlength=self.ts.num_nodes,
468-
)
469-
node_spans = self.ts.sample_count_stat(
470-
[self.ts.samples()],
471-
lambda x: (x > 0),
472-
1,
473-
polarised=True,
474-
span_normalise=False,
475-
strict=False,
476-
mode="node",
477-
)[:, 0]
478-
return span_sums / node_spans

pages/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import os
21
import importlib
2+
import os
33

44
# List all files in the current directory
55
for module_file in os.listdir(os.path.dirname(__file__)):
66
# Check if it's a python file and not this __init__ file
7-
if module_file.endswith('.py') and module_file != '__init__.py':
7+
if module_file.endswith(".py") and module_file != "__init__.py":
88
module_name = module_file[:-3] # remove the .py extension
9-
module = importlib.import_module('.' + module_name, package=__name__)
9+
module = importlib.import_module("." + module_name, package=__name__)
1010

1111
# Add the page function to the current module's namespace
1212
globals()[module_name] = module.page

pages/edge_explorer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import panel as pn
2-
import holoviews as hv
31
import bokeh.models as bkm
2+
import holoviews as hv
3+
import panel as pn
4+
45
import config
56

7+
68
def page(tsm):
79
hv.extension("bokeh")
810
edges_df = tsm.edges_df

pages/edges.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import bokeh.models as bkm
12
import holoviews as hv
23
import holoviews.operation.datashader as hd
34
import panel as pn
5+
46
import config
5-
import bokeh.models as bkm
6-
from plot_helpers import filter_points, hover_points
7+
from plot_helpers import filter_points
8+
from plot_helpers import hover_points
9+
710

811
def page(tsm):
912
hv.extension("bokeh")

pages/mutations.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import panel as pn
21
import holoviews as hv
3-
import config
42
import holoviews.operation.datashader as hd
3+
import hvplot.pandas # noqa
54
import numpy as np
6-
from plot_helpers import filter_points, hover_points
5+
import panel as pn
6+
7+
import config
8+
from plot_helpers import filter_points
9+
from plot_helpers import hover_points
10+
711

812
def make_hist_on_axis(dimension, points, num_bins=30):
9-
### Make histogram function for a specified axis of a scatter plot
13+
"""
14+
Make histogram function for a specified axis of a scatter plot
15+
"""
16+
1017
def compute_hist(x_range, y_range):
1118
filtered_points = filter_points(points, x_range, y_range)
1219
hist = hv.operation.histogram(
@@ -17,9 +24,10 @@ def compute_hist(x_range, y_range):
1724
return compute_hist
1825

1926

20-
2127
def make_hist(data, title, bins_range, log_y=True, plot_width=800):
22-
### Make histogram from given count data
28+
"""
29+
Make histogram from given count data
30+
"""
2331
count, bins = np.histogram(data, bins=bins_range)
2432
ylabel = "log(Count)" if log_y else "Count"
2533
np.seterr(divide="ignore")
@@ -34,7 +42,9 @@ def make_hist(data, title, bins_range, log_y=True, plot_width=800):
3442

3543

3644
def make_hist_panel(tsm, log_y):
37-
### Make row of histograms for holoviews panel
45+
"""
46+
Make row of histograms for holoviews panel
47+
"""
3848
overall_site_hist = make_hist(
3949
tsm.sites_num_mutations,
4050
"Mutations per site",
@@ -51,6 +61,7 @@ def make_hist_panel(tsm, log_y):
5161
)
5262
return pn.Row(overall_site_hist, overall_node_hist)
5363

64+
5465
def page(tsm):
5566
hv.extension("bokeh")
5667
plot_width = 1000

pages/nodes.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import functools
2+
3+
import holoviews as hv
4+
import holoviews.operation.datashader as hd
5+
import hvplot.pandas # noqa
6+
import numpy as np
7+
import panel as pn
8+
9+
import config
10+
from plot_helpers import filter_points
11+
from plot_helpers import hover_points
12+
from plot_helpers import make_hist_matplotlib
13+
14+
15+
def page(tsm):
16+
hv.extension("matplotlib")
17+
df_nodes = tsm.nodes_df
18+
df_internal_nodes = df_nodes[
19+
(df_nodes.is_sample == 0) & (df_nodes.ancestors_span != -np.inf)
20+
]
21+
bins = min(50, int(np.sqrt(len(df_internal_nodes))))
22+
23+
ancestor_spans_hist_func = functools.partial(
24+
make_hist_matplotlib,
25+
df_internal_nodes.ancestors_span,
26+
"Ancestor spans per node",
27+
num_bins=bins,
28+
log_y=True,
29+
)
30+
31+
log_y_checkbox = pn.widgets.Checkbox(name="log y-axis of histogram", value=True)
32+
33+
ancestor_spans_hist_panel = pn.bind(
34+
ancestor_spans_hist_func,
35+
log_y=log_y_checkbox,
36+
)
37+
38+
hist_panel = pn.Column(
39+
ancestor_spans_hist_panel,
40+
)
41+
42+
hv.extension("bokeh")
43+
points = df_nodes.hvplot.scatter(
44+
x="ancestors_span",
45+
y="time",
46+
hover_cols=["ancestors_span", "time"],
47+
).opts(width=config.PLOT_WIDTH, height=config.PLOT_HEIGHT)
48+
49+
range_stream = hv.streams.RangeXY(source=points)
50+
streams = [range_stream]
51+
filtered = points.apply(filter_points, streams=streams)
52+
hover = filtered.apply(hover_points, threshold=config.THRESHOLD)
53+
shaded = hd.datashade(filtered, width=400, height=400, streams=streams)
54+
55+
main = (shaded * hover).opts(
56+
hv.opts.Points(tools=["hover"], alpha=0.1, hover_alpha=0.2, size=10)
57+
)
58+
59+
plot_options = pn.Column(
60+
pn.pane.Markdown("# Plot Options"),
61+
log_y_checkbox,
62+
)
63+
return pn.Column(main, hist_panel, plot_options)

pages/overview.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import panel as pn
22

3+
34
def page(tsm):
4-
return pn.pane.HTML(tsm.ts)
5+
return pn.pane.HTML(tsm.ts)

0 commit comments

Comments
 (0)