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

Lazy Streaming of Tabs #291

Open
wants to merge 52 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b2563e6
changed button to tab and lazily load widget
cjachekang Feb 16, 2021
381b129
adding tabbing
cjachekang Feb 17, 2021
f851096
removed comments
cjachekang Feb 21, 2021
bcb8917
Merge branch 'master' into lazy_stream
cjachekang Feb 21, 2021
b024cfa
some progress on lazy streaming
cjachekang Feb 22, 2021
f90d5b5
added datetime check for temporal action before processing
cjachekang Feb 23, 2021
2c2a59f
reversed order of actions to put correlation at the back
cjachekang Feb 23, 2021
83a19d2
changed to pushing out tabs as they are done
cjachekang Mar 2, 2021
4c59f84
Merge branch 'master' into lazy_stream
cjachekang Mar 2, 2021
54083b7
debugging intent
cjachekang Mar 2, 2021
bd27bd9
fixed intent bug
cjachekang Mar 2, 2021
693252d
changed tests to use set and fixed bugs
cjachekang Mar 2, 2021
0687fd7
added bakc if statement
cjachekang Mar 2, 2021
f6378ea
updated test due to ordering of tabs
cjachekang Mar 2, 2021
a1e56fd
fixed merge conflicts
cjachekang Mar 3, 2021
bda3c4b
fixed merge conflicts
cjachekang Mar 4, 2021
1d2555e
Merge branch 'master' into lazy_stream
cjachekang Mar 7, 2021
a420a64
added config to turn off streaming, support for greyed tabs
cjachekang Mar 11, 2021
9398896
action logic
cjachekang Mar 14, 2021
1b5c923
changed to pre-displaying tabs
cjachekang Mar 14, 2021
4d2be94
override css
cjachekang Mar 14, 2021
3fe6e68
Merge branch 'master' into lazy_stream
cjachekang Mar 14, 2021
e60ef84
reformatted with black
cjachekang Mar 14, 2021
86d6c91
code cleanup
cjachekang Mar 14, 2021
e61bda7
changed back to repr_html
cjachekang Mar 14, 2021
f772fc7
fixed html
cjachekang Mar 14, 2021
c485d2d
reformatted with black
cjachekang Mar 14, 2021
129d7c8
fixed merge conflicts
cjachekang Apr 28, 2021
593e6b0
some updates
cjachekang Apr 29, 2021
7dd15b2
loadingbar added
cjachekang Apr 29, 2021
a6d05ba
merged into master
cjachekang Apr 29, 2021
a70a9e7
latest commit
cjachekang Apr 29, 2021
b2f272f
Delete communities.csv
cjachekang Apr 29, 2021
c60cfc0
Fixed a couple merge conflicts
cjachekang Apr 29, 2021
cc4129b
added in css for pandas
cjachekang Apr 30, 2021
e6c5720
merged into master
cjachekang May 1, 2021
f226cca
formatted with black
cjachekang May 1, 2021
3ef1f7b
fixed some tests
cjachekang May 3, 2021
0c34bb5
updated display
cjachekang May 3, 2021
aef50f8
progress on temporal
cjachekang May 4, 2021
4d32f1f
fixed pandas coverage tests
cjachekang May 4, 2021
45f8330
Merge branch 'master' into lazy_stream
cjachekang May 4, 2021
8493b91
progress
cjachekang May 5, 2021
00bdf3d
Merge branch 'master' into lazy_stream
cjachekang May 5, 2021
07c47ba
fixed last tests
cjachekang May 5, 2021
9ce4681
readded output widget for intent button
cjachekang May 5, 2021
998086a
updated per comments
cjachekang May 6, 2021
5aea05b
reformatted with black
cjachekang May 6, 2021
16d2880
fixed merge conflicts
cjachekang Jun 28, 2021
60aab30
fixed loading bar bug
cjachekang Jun 30, 2021
c74b1aa
Merge branch 'master' into lazy_stream
cjachekang Jul 1, 2021
5f0f9c2
ran black
cjachekang Jul 3, 2021
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
39 changes: 22 additions & 17 deletions lux/action/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,36 +45,41 @@ def custom(ldf):
lux.config.executor.execute(vlist, ldf)
for vis in vlist:
vis.score = interestingness(vis, ldf)
# ldf.clear_intent()
vlist.sort(remove_invalid=True)
return recommendation


def custom_actions(ldf):
def custom_action(ldf, action):
"""
Generates user-defined vis based on globally defined actions.
Computing initial custom_action for lazy streaming of the rest of the actions

Parameters
----------
ldf : lux.core.frame
LuxDataFrame with underspecified intent.

action: action_name as string
e.g "Correlation"

Returns
-------
recommendations : Dict[str,obj]
object with a collection of visualizations that were previously registered.
One recommendation
"""
if len(lux.config.actions) > 0 and len(ldf) > 0:
recommendations = []
recommendation = None
display_condition = lux.config.actions[action].display_condition
if (display_condition is None or (display_condition is not None and display_condition(ldf))):
args = lux.config.actions[action].args
if args:
recommendation = lux.config.actions[action].action(ldf, args)
else:
recommendation = lux.config.actions[action].action(ldf)
return recommendation

def filter_keys(ldf):
keys = []
if len(ldf) > 0:
for action_name in lux.config.actions.keys():
display_condition = lux.config.actions[action_name].display_condition
if display_condition is None or (display_condition is not None and display_condition(ldf)):
args = lux.config.actions[action_name].args
if args:
recommendation = lux.config.actions[action_name].action(ldf, args)
else:
recommendation = lux.config.actions[action_name].action(ldf)
recommendations.append(recommendation)
return recommendations
else:
return []
keys.insert(0, action_name)
return keys

102 changes: 69 additions & 33 deletions lux/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ def recommendation(self):
self.maintain_metadata()
self.current_vis = Compiler.compile_intent(self, self._intent)
self.maintain_recs()
self.compute_remaining_actions()
return self._recommendation

@recommendation.setter
Expand Down Expand Up @@ -477,7 +478,6 @@ def maintain_recs(self, is_series="DataFrame"):
rec_infolist = []
from lux.action.row_group import row_group
from lux.action.column_group import column_group

# TODO: Rewrite these as register action inside default actions
if rec_df.pre_aggregated:
if rec_df.columns.name is not None:
Expand All @@ -486,13 +486,18 @@ def maintain_recs(self, is_series="DataFrame"):
elif not (len(rec_df) < 5 and not rec_df.pre_aggregated) and not (
self.index.nlevels >= 2 or self.columns.nlevels >= 2
):
from lux.action.custom import custom_actions
from lux.action.custom import custom_action, filter_keys

self.action_keys = filter_keys(rec_df)
action_index = 0
lenOfKeys = len(self.action_keys)
# generate vis from globally registered actions and append to dataframe
custom_action_collection = custom_actions(rec_df)
for rec in custom_action_collection:
# Need to iteratre through tabs that might not be computed in some cases (e.g Temporal)
while rec_infolist == [] and self.action_keys and action_index < lenOfKeys:
rec = custom_action(rec_df, self.action_keys[action_index])
rec_df._append_rec(rec_infolist, rec)
lux.config.update_actions["flag"] = False
lux.config.update_actions["flag"] = False
self.action_keys.pop(action_index)

# Store _rec_info into a more user-friendly dictionary form
rec_df._recommendation = {}
Expand All @@ -502,7 +507,9 @@ def maintain_recs(self, is_series="DataFrame"):
if len(vlist) > 0:
rec_df._recommendation[action_type] = vlist
rec_df._rec_info = rec_infolist

self._widget = rec_df.render_widget()

# re-render widget for the current dataframe if previous rec is not recomputed
elif show_prev:
self._widget = rec_df.render_widget()
Expand Down Expand Up @@ -607,11 +614,11 @@ def set_intent_on_click(self, change):
intent_action = list(self._widget.selectedIntentIndex.keys())[0]
vis = self._recommendation[intent_action][self._widget.selectedIntentIndex[intent_action][0]]
self.set_intent_as_vis(vis)

self.maintain_metadata()
self.current_vis = Compiler.compile_intent(self, self._intent)
self.maintain_recs()


if len(self._widget.recommendations) <= 1:
self.compute_remaining_actions()

with self.output:
clear_output()
display(self._widget)
Expand All @@ -628,7 +635,32 @@ def _repr_html_(self):
if self._pandas_only:
display(self.display_pandas())
self._pandas_only = False
# else:

# Pre-displaying initial pandas frame before computing widget
pandas_output = widgets.Output()
self.output = widgets.Output()

with pandas_output:
display(self.display_pandas())

with self.output:
display(widgets.HTML(value="Loading widget..."))

if lux.config.default_display == "lux":
tab_contents = ['Lux', 'Pandas']
children = [self.output, pandas_output]
else:
tab_contents = ['Pandas', 'Lux']
children = [pandas_output, self.output]

tab = widgets.Tab()
tab.children = children

for i in range(len(tab_contents)):
tab.set_title(i, tab_contents[i])

display(tab)

if not self.index.nlevels >= 2 or self.columns.nlevels >= 2:
self.maintain_metadata()

Expand All @@ -637,44 +669,26 @@ def _repr_html_(self):

self.current_vis = Compiler.compile_intent(self, self._intent)

if lux.config.default_display == "lux":
self._toggle_pandas_display = False
else:
self._toggle_pandas_display = True

# df_to_display.maintain_recs() # compute the recommendations (TODO: This can be rendered in another thread in the background to populate self._widget)
self.maintain_recs()

# Observers(callback_function, listen_to_this_variable)
self._widget.observe(self.remove_deleted_recs, names="deletedIndices")
self._widget.observe(self.set_intent_on_click, names="selectedIntentIndex")

button = widgets.Button(
description="Toggle Pandas/Lux",
layout=widgets.Layout(width="140px", top="5px"),
)
self.output = widgets.Output()
display(button, self.output)

def on_button_clicked(b):
if len(self._recommendation) > 0:
with self.output:
if b:
self._toggle_pandas_display = not self._toggle_pandas_display
clear_output()
if self._toggle_pandas_display:
display(self.display_pandas())
else:
# b.layout.display = "none"
display(self._widget)
# b.layout.display = "inline-block"
display(self._widget)

button.on_click(on_button_clicked)
on_button_clicked(None)
if len(self._widget.recommendations) <= 1 and hasattr(self, "action_keys"):
self.compute_remaining_actions()

except (KeyboardInterrupt, SystemExit):
raise
except Exception:
if lux.config.pandas_fallback:
clear_output()
warnings.warn(
"\nUnexpected error in rendering Lux widget and recommendations. "
"Falling back to Pandas display.\n"
Expand All @@ -685,6 +699,26 @@ def on_button_clicked(b):
display(self.display_pandas())
else:
raise

def compute_remaining_actions(self):
# Lazily load the rest of the tabs
from lux.action.custom import custom_action

i = 1
for action_name in self.action_keys:
rec = custom_action(self, action_name)
if (len(rec["collection"])) > 0:
self._append_rec(self._rec_info, rec)

vlist = self._rec_info[i]["collection"]
if len(vlist) > 0:
self._recommendation[rec["action"]] = vlist

new_widget = self.render_widget()
self._widget.recommendations = new_widget.recommendations
self._widget.loadNewTab = action_name

i += 1

def display_pandas(self):
return self.to_pandas()
Expand Down Expand Up @@ -798,6 +832,7 @@ def rec_to_JSON(recs):
rec_lst.append(rec)
# delete since not JSON serializable
del rec_lst[idx]["collection"]

return rec_lst

def save_as_html(self, filename: str = "export.html") -> None:
Expand All @@ -813,6 +848,7 @@ def save_as_html(self, filename: str = "export.html") -> None:
if self.widget is None:
self.maintain_metadata()
self.maintain_recs()
self.compute_remaining_actions()

from ipywidgets.embed import embed_data

Expand Down
61 changes: 33 additions & 28 deletions lux/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,39 @@ def __repr__(self):
print(series_repr)
ldf._pandas_only = False
else:

# Pre-displaying initial pandas frame before computing widget
pandas_output = widgets.Output()
ldf.output = widgets.Output()

with pandas_output:
display(ldf.display_pandas())

with ldf.output:
display(widgets.HTML(value="Loading widget..."))

if not self.index.nlevels >= 2:
ldf.maintain_metadata()

if lux.config.default_display == "lux":
self._toggle_pandas_display = False
tab_contents = ['Lux', 'Pandas']
children = [ldf.output, pandas_output]
else:
self._toggle_pandas_display = True
tab_contents = ['Pandas', 'Lux']
children = [pandas_output, ldf.output]

tab = widgets.Tab()
tab.children = children

for i in range(len(tab_contents)):
tab.set_title(i, tab_contents[i])

display(tab)

if ldf._intent != [] and (not hasattr(ldf, "_compiled") or not ldf._compiled):
from lux.processor.Compiler import Compiler

ldf.current_vis = Compiler.compile_intent(ldf, ldf._intent)

# df_to_display.maintain_recs() # compute the recommendations (TODO: This can be rendered in another thread in the background to populate self._widget)
ldf.maintain_recs(is_series="Series")
Expand All @@ -148,34 +174,13 @@ def __repr__(self):
ldf._widget.observe(ldf.remove_deleted_recs, names="deletedIndices")
ldf._widget.observe(ldf.set_intent_on_click, names="selectedIntentIndex")

self._widget = ldf._widget
self._recommendation = ldf._recommendation

# box = widgets.Box(layout=widgets.Layout(display='inline'))
button = widgets.Button(
description="Toggle Pandas/Lux",
layout=widgets.Layout(width="140px", top="5px"),
)
ldf.output = widgets.Output()
# box.children = [button,output]
# output.children = [button]
# display(box)
display(button, ldf.output)

def on_button_clicked(b):
if len(ldf._recommendation) > 0:
with ldf.output:
if b:
self._toggle_pandas_display = not self._toggle_pandas_display
clear_output()
if self._toggle_pandas_display:
print(series_repr)
else:
# b.layout.display = "none"
display(ldf._widget)
# b.layout.display = "inline-block"

button.on_click(on_button_clicked)
on_button_clicked(None)
display(ldf._widget)

if len(ldf._widget.recommendations) <= 1 and hasattr(ldf, "action_keys"):
ldf.compute_remaining_actions()

except (KeyboardInterrupt, SystemExit):
raise
Expand Down
4 changes: 2 additions & 2 deletions tests/test_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def test_custom_aggregation(global_var):
df = pytest.college_df
df.set_intent(["HighestDegree", lux.Clause("AverageCost", aggregation=np.ptp)])
df._repr_html_()
assert list(df.recommendation.keys()) == ["Enhance", "Filter", "Generalize"]
assert set(df.recommendation.keys()) == set(["Enhance", "Filter", "Generalize"])
df.clear_intent()


Expand Down Expand Up @@ -273,4 +273,4 @@ def test_intent_retained():
assert df._metadata_fresh == False

df._repr_html_()
assert list(df.recommendation.keys()) == ["Enhance", "Filter"]
assert set(df.recommendation.keys()) == set(["Enhance", "Filter"])
10 changes: 5 additions & 5 deletions tests/test_columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,22 @@ def test_abbrev_agg():
def test_int_columns(global_var):
df = pd.read_csv("lux/data/college.csv")
df.columns = range(len(df.columns))
assert list(df.recommendation.keys()) == ["Correlation", "Distribution", "Occurrence"]
assert set(df.recommendation.keys()) == set(["Correlation", "Distribution", "Occurrence"])
df.intent = [8, 3]
assert list(df.recommendation.keys()) == ["Enhance", "Filter", "Generalize"]
assert set(df.recommendation.keys()) == set(["Enhance", "Filter", "Generalize"])
df.intent = [0]
assert list(df.recommendation.keys()) == ["Enhance", "Filter"]
assert set(df.recommendation.keys()) == set(["Enhance", "Filter"])


def test_name_column(global_var):
df = pd.read_csv("lux/data/car.csv")
new_df = df.rename(columns={"Name": "name"})
assert list(new_df.recommendation.keys()) == [
assert set(new_df.recommendation.keys()) == set([
"Correlation",
"Distribution",
"Occurrence",
"Temporal",
]
])
assert len(new_df.recommendation["Correlation"])
assert new_df["name"][0] != None
assert (new_df["name"].unique() != None)[0]
2 changes: 1 addition & 1 deletion tests/test_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ def test_convert_dtype(global_var):
df = pytest.college_df
cdf = df.convert_dtypes()
cdf._repr_html_()
assert list(cdf.recommendation.keys()) == ["Correlation", "Distribution", "Occurrence"]
assert set(cdf.recommendation.keys()) == set(["Correlation", "Distribution", "Occurrence"])
Loading