Skip to content

Commit

Permalink
Progress bar (#188)
Browse files Browse the repository at this point in the history
* Update progressbar

* Add docstrings

* Save notebook

* Update notebooks

* Update progress bar

* fix tests

* move import to module

Co-authored-by: Matan Perlmutter <matan@deepchecks.com>
  • Loading branch information
matanper and Matan Perlmutter committed Nov 25, 2021
1 parent a1ce389 commit 2a8f35c
Show file tree
Hide file tree
Showing 9 changed files with 774 additions and 786 deletions.
7 changes: 6 additions & 1 deletion deepchecks/base/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def __init__(self, value, header: str = None, check=None, display: Any = None):
display (List): Objects to be displayed (dataframe or function or html)
"""
self.value = value
self.header = header or (check and split_camel_case(check.__name__)) or None
self.header = header or (check and check.name()) or None
self.check = check
self.condition_results = []

Expand Down Expand Up @@ -270,6 +270,11 @@ def remove_condition(self, index: int):
raise DeepchecksValueError(f'Index {index} of conditions does not exists')
self._conditions.pop(index)

@classmethod
def name(cls):
"""Name of class in split camel case."""
return split_camel_case(cls.__name__)


class SingleDatasetBaseCheck(BaseCheck):
"""Parent class for checks that only use one dataset."""
Expand Down
106 changes: 33 additions & 73 deletions deepchecks/base/display_suite.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
"""Handle display of suite result."""
# pylint: disable=protected-access
import sys
import tqdm
from typing import List, Union

from IPython.core.display import display_html

from deepchecks.base.check import CheckResult, CheckFailure
from deepchecks.base.display_pandas import dataframe_to_html, display_dataframe
from deepchecks.string_utils import split_camel_case
from deepchecks.utils import is_widgets_enabled
import pandas as pd

__all__ = ['display_suite_result_1', 'display_suite_result_2']
__all__ = ['display_suite_result', 'ProgressBar']


class ProgressBar:
"""Progress bar for display while running suite."""

def __init__(self, name, length):
"""Initialize progress bar."""
shared_args = {'total': length, 'desc': name, 'unit': ' Check', 'leave': False, 'file': sys.stdout}
if is_widgets_enabled():
self.pbar = tqdm.tqdm_notebook(**shared_args, colour='#9d60fb')
else:
# Normal tqdm with colour in notebooks produce bug that the cleanup doesn't remove all characters. so
# until bug fixed, doesn't add the colour to regular tqdm
self.pbar = tqdm.tqdm(**shared_args, bar_format=f'{{l_bar}}{{bar:{length}}}{{r_bar}}')

def set_text(self, text):
"""Set current running check."""
self.pbar.set_postfix(Check=text)

def close(self):
"""Close the progress bar."""
self.pbar.close()

def inc_progress(self):
"""Increase progress bar value by 1."""
self.pbar.update(1)


def get_display_exists_icon(exists: bool):
Expand All @@ -18,75 +46,7 @@ def get_display_exists_icon(exists: bool):
return '<div style="text-align: center">No</div>'


def display_suite_result_1(name: str, results: List[Union[CheckResult, CheckFailure]]):
"""Display results of suite in IPython."""
display_html(f'<h1>{name}</h1>', raw=True)
conditions_table = []
checks_without_condition_table = []
errors_table = []

for result in results:
if isinstance(result, CheckResult):
if result.have_conditions():
for cond_result in result.conditions_results:
sort_value = cond_result.get_sort_value()
icon = cond_result.get_icon()
conditions_table.append([icon, result.header, cond_result.name,
cond_result.details, sort_value])
else:
checks_without_condition_table.append([result.header,
get_display_exists_icon(result.have_display())])
elif isinstance(result, CheckFailure):
errors_table.append([str(result.check), str(result.exception), result.exception.__class__.__name__])

# First print summary
display_html('<h2>Checks Summary</h2>', raw=True)
if conditions_table:
display_html('<h3>With Conditions</h3>'
'<p>Checks which have defined conditions, which constitute some limitation on the result'
' of the check. The status defines if the limitation passed or not.</p>', raw=True)
table = pd.DataFrame(data=conditions_table, columns=['Status', 'Check', 'Condition', 'More Info', 'sort'])
table.sort_values(by=['sort'], inplace=True)
table.drop('sort', axis=1, inplace=True)
display_dataframe(table)
if checks_without_condition_table:
display_html('<h3>Without Conditions</h3>'
'<p>Checks which does not have defined condition. If an interesting result have been found '
'it will be displayed below in the next section of "Display Results"</p>', raw=True)
table = pd.DataFrame(data=checks_without_condition_table, columns=['Check', 'Has Display?'])
display_dataframe(table)
if errors_table:
display_html('<h3>With Error</h3><p>Checks which raised an error during their run</p>', raw=True)
table = pd.DataFrame(data=errors_table, columns=['Check', 'Error', 'Type'])
display_dataframe(table)

only_check_with_display = [r for r in results
if isinstance(r, CheckResult) and r.have_display()]
# If there are no checks with display doesn't print anything else
if only_check_with_display:
checks_not_passed = [r for r in only_check_with_display
if r.have_conditions() and not r.passed_conditions()]
checks_without_condition = [r for r in only_check_with_display
if not r.have_conditions() and r.have_display()]
checks_passed = [r for r in only_check_with_display
if r.have_conditions() and r.passed_conditions() and r.have_display()]

display_html('<hr><h2>Results Display</h2>', raw=True)
if checks_not_passed:
display_html('<h3>Checks with Failed Condition</h3>', raw=True)
for result in sorted(checks_not_passed, key=lambda x: x.get_conditions_sort_value()):
result._ipython_display_()
if checks_without_condition:
display_html('<h3>Checks without Condition</h3>', raw=True)
for result in checks_without_condition:
result._ipython_display_()
if checks_passed:
display_html('<h3>Checks with Passed Condition</h3>', raw=True)
for result in checks_passed:
result._ipython_display_()


def display_suite_result_2(suite_name: str, results: List[Union[CheckResult, CheckFailure]]):
def display_suite_result(suite_name: str, results: List[Union[CheckResult, CheckFailure]]):
"""Display results of suite in IPython."""
conditions_table = []
display_table = []
Expand All @@ -105,7 +65,7 @@ def display_suite_result_2(suite_name: str, results: List[Union[CheckResult, Che
others_table.append([result.header, 'Nothing found', 2])
elif isinstance(result, CheckFailure):
msg = result.exception.__class__.__name__ + ': ' + str(result.exception)
name = split_camel_case(result.check.__name__)
name = result.check.name()
others_table.append([name, msg, 1])

light_hr = '<hr style="background-color: #eee;border: 0 none;color: #eee;height: 1px;">'
Expand Down Expand Up @@ -157,7 +117,7 @@ def get_first_3(results: List[Union[CheckResult, CheckFailure]]):
i = 0
while len(first_3) < 3 and i < len(results):
curr = results[i]
curr_name = split_camel_case(curr.check.__name__)
curr_name = curr.check.name()
if curr_name not in first_3:
first_3.append(curr_name)
i += 1
Expand Down
26 changes: 6 additions & 20 deletions deepchecks/base/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
from collections import OrderedDict
from typing import Union, List, Optional

from IPython.core.display import display
from ipywidgets import IntProgress, HTML, VBox

from deepchecks.base.display_suite import display_suite_result_2
from deepchecks.utils import DeepchecksValueError, is_widgets_enabled
from deepchecks.base.display_suite import display_suite_result, ProgressBar
from deepchecks.utils import DeepchecksValueError
from deepchecks.base import Dataset
from deepchecks.base.check import (
BaseCheck, CheckResult, TrainTestBaseCheck, CompareDatasetsBaseCheck,
Expand All @@ -30,7 +27,7 @@ def __init__(self, name: str, results):
self.results = results

def _ipython_display_(self):
display_suite_result_2(self.name, self.results)
display_suite_result(self.name, self.results)


class CheckSuite(BaseCheck):
Expand Down Expand Up @@ -83,17 +80,13 @@ def run(
raise ValueError('At least one dataset (or model) must be passed to the method!')

# Create progress bar
progress_bar = IntProgress(value=0, min=0, max=len(self.checks),
bar_style='info', style={'bar_color': '#9d60fb'}, orientation='horizontal')
label = HTML()
box = VBox(children=[label, progress_bar])
self._display_widget(box)
progress_bar = ProgressBar(self.name, len(self.checks))

# Run all checks
results = []
for check in self.checks.values():
try:
label.value = f'Running {str(check)}'
progress_bar.set_text(check.name())
if isinstance(check, TrainTestBaseCheck):
if train_dataset is not None and test_dataset is not None:
check_result = check.run(train_dataset=train_dataset, test_dataset=test_dataset,
Expand Down Expand Up @@ -126,12 +119,9 @@ def run(
raise TypeError(f'Don\'t know how to handle type {check.__class__.__name__} in suite.')
except Exception as exp:
results.append(CheckFailure(check.__class__, exp))
progress_bar.value = progress_bar.value + 1
progress_bar.inc_progress()

progress_bar.close()
label.close()
box.close()

return SuiteResult(self.name, results)

def __repr__(self, tabs=0):
Expand Down Expand Up @@ -176,7 +166,3 @@ def remove(self, index: int):
raise DeepchecksValueError(f'No index {index} in suite')
self.checks.pop(index)
return self

def _display_widget(self, param):
if is_widgets_enabled():
display(param)
2 changes: 1 addition & 1 deletion deepchecks/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def is_widgets_enabled():
# disabled and enabled, regard it as disabled
output = subprocess.getoutput('jupyter nbextension list').split('\n')
disabled_regex = re.compile(r'\s*(jupyter-js-widgets/extension).*(disabled).*')
enabled_regex = re.compile(r'\s*(jupyter-js-widgets/extension).*(disabled).*')
enabled_regex = re.compile(r'\s*(jupyter-js-widgets/extension).*(enabled).*')
found_disabled = any((disabled_regex.match(s) for s in output))
found_enabled = any((enabled_regex.match(s) for s in output))
return not found_disabled and found_enabled
Expand Down
3 changes: 3 additions & 0 deletions notebooks/.nbval-sanitizer
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ replace: _ELE_
regex: (\u0023|\=)[a-zA-Z0-9\-\_]+
replace: random

[inconsistent-whitespace-tqdm]
regex: ^\s+\r$
replace: \r

[date_stamp]
regex: \d{1,4}(\\|\/|-)(\d{1,2}|\w{3})((\\|\/|-)\d{1,4})?
Expand Down

0 comments on commit 2a8f35c

Please sign in to comment.