Skip to content

Commit

Permalink
improvements to log chart (#83)
Browse files Browse the repository at this point in the history
* moved log chart to api submodule, added support for bokeh and altair, added log_table

* fixed formatting errors

* added log_html and reinstated the old xgboost_monitor file with a deprecation warning
  • Loading branch information
jakubczakon authored May 11, 2020
1 parent 3154948 commit 0a9e3a9
Show file tree
Hide file tree
Showing 17 changed files with 755 additions and 385 deletions.
32 changes: 32 additions & 0 deletions neptunecontrib/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,35 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#

from neptunecontrib.api.chart import log_chart
from neptunecontrib.api.html import log_html
from neptunecontrib.api.table import log_table
from neptunecontrib.api.utils import (
concat_experiments_on_channel,
extract_project_progress_info,
get_channel_columns,
get_parameter_columns,
get_property_columns,
get_system_columns,
strip_prefices,
pickle_and_log_artifact,
get_pickled_artifact,
get_filepaths
)

__all__ = [
'log_table',
'log_html',
'log_chart',
'concat_experiments_on_channel',
'extract_project_progress_info',
'get_channel_columns',
'get_parameter_columns',
'get_property_columns',
'get_system_columns',
'strip_prefices',
'pickle_and_log_artifact',
'get_pickled_artifact',
'get_filepaths'
]
202 changes: 202 additions & 0 deletions neptunecontrib/api/chart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#
# Copyright (c) 2020, Neptune Labs Sp. z o.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import neptune

__all__ = [
'log_chart',
]


def log_chart(name, chart, experiment=None):
"""Logs charts from matplotlib, plotly, bokeh, and altair to neptune.
Plotly, Bokeh, and Altair charts are converted to interactive HTML objects and then uploaded to Neptune
as an artifact with path charts/{name}.html.
Matplotlib figures are converted optionally. If plotly is installed, matplotlib figures are converted
to plotly figures and then converted to interactive HTML and uploaded to Neptune as an artifact with
path charts/{name}.html. If plotly is not installed, matplotlib figures are converted to PNG images
and uploaded to Neptune as an artifact with path charts/{name}.png
Args:
name (:obj:`str`):
| Name of the chart (without extension) that will be used as a part of artifact's destination.
chart (:obj:`matplotlib` or :obj:`plotly` Figure):
| Figure from `matplotlib` or `plotly`. If you want to use global figure from `matplotlib`, you
can also pass reference to `matplotlib.pyplot` module.
experiment (:obj:`neptune.experiments.Experiment`, optional, default is ``None``):
| For advanced users only. Pass Neptune
`Experiment <https://docs.neptune.ai/neptune-client/docs/experiment.html#neptune.experiments.Experiment>`_
object if you want to control to which experiment data is logged.
| If ``None``, log to currently active, and most recent experiment.
Examples:
Start an experiment::
import neptune
neptune.init(api_token='ANONYMOUS',
project_qualified_name='shared/showroom')
neptune.create_experiment(name='experiment_with_charts')
Create matplotlib figure and log it to Neptune::
import matplotlib.pyplot as plt
fig = plt.figure()
x = [21,22,23,4,5,6,77,8,9,10,31,32,33,34,35,36,37,18,49,50,100]
plt.hist(x, bins=5)
plt.show()
from neptunecontrib.logging.chart import log_chart
log_chart('matplotlib_figure', fig)
Create Plotly chart and log it to Neptune::
import plotly.express as px
df = px.data.tips()
fig = px.histogram(df, x="total_bill", y="tip", color="sex", marginal="rug",
hover_data=df.columns)
fig.show()
from neptunecontrib.logging.chart import log_chart
log_chart('plotly_figure', fig)
Create Altair chart and log it to Neptune::
import altair as alt
from vega_datasets import data
source = data.cars()
chart = alt.Chart(source).mark_circle(size=60).encode(
x='Horsepower',
y='Miles_per_Gallon',
color='Origin',
tooltip=['Name', 'Origin', 'Horsepower', 'Miles_per_Gallon']
).interactive()
from neptunecontrib.logging.chart import log_chart
log_chart('altair_chart', chart)
Create Bokeh figure and log it to Neptune::
from bokeh.plotting import figure
p = figure(plot_width=400, plot_height=400)
# add a circle renderer with a size, color, and alpha
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
from neptunecontrib.logging.chart import log_chart
log_chart('bokeh_figure', p)
Check out how the logged charts look in Neptune:
https://ui.neptune.ai/o/shared/org/showroom/e/SHOW-973/artifacts?path=charts%2F&file=bokeh_figure.html
"""
_exp = experiment if experiment else neptune

if is_matplotlib_pyplot(chart) or is_matplotlib_figure(chart):
if is_matplotlib_pyplot(chart):
chart = chart.gcf()

try:
from plotly import tools
chart = tools.mpl_to_plotly(chart)

_exp.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html')
except ImportError:
_exp.log_artifact(export_matplotlib_figure(chart), "charts/" + name + '.png')

elif is_plotly_figure(chart):
_exp.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html')

elif is_bokeh_figure(chart):
_exp.log_artifact(export_bokeh_figure(chart), "charts/" + name + '.html')

elif is_altair_chart(chart):
_exp.log_artifact(export_altair_chart(chart), "charts/" + name + '.html')

else:
raise ValueError("Currently supported are matplotlib, plotly, altair, and bokeh figures")


def is_matplotlib_pyplot(chart):
return hasattr(chart, '__name__') and chart.__name__.startswith('matplotlib.')


def is_matplotlib_figure(chart):
return chart.__class__.__module__.startswith('matplotlib.') and chart.__class__.__name__ == 'Figure'


def is_plotly_figure(chart):
return chart.__class__.__module__.startswith('plotly.') and chart.__class__.__name__ == 'Figure'


def is_altair_chart(chart):
return chart.__class__.__module__.startswith('altair.') and 'Chart' in chart.__class__.__name__


def is_bokeh_figure(chart):
return chart.__class__.__module__.startswith('bokeh.') and chart.__class__.__name__ == 'Figure'


def export_plotly_figure(chart):
from io import StringIO

buffer = StringIO()
chart.write_html(buffer)
buffer.seek(0)

return buffer


def export_matplotlib_figure(chart):
from io import BytesIO

buffer = BytesIO()
chart.savefig(buffer, format='png')
buffer.seek(0)

return buffer


def export_altair_chart(chart):
from io import StringIO

buffer = StringIO()
chart.save(buffer, format='html')
buffer.seek(0)

return buffer


def export_bokeh_figure(chart):
from io import StringIO
from bokeh.resources import CDN
from bokeh.embed import file_html

html = file_html(chart, CDN)
buffer = StringIO(html)
buffer.seek(0)

return buffer
74 changes: 74 additions & 0 deletions neptunecontrib/api/html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#
# Copyright (c) 2020, Neptune Labs Sp. z o.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import neptune

__all__ = [
'log_html',
]


def log_html(name, html, experiment=None):
"""Logs html to neptune.
HTML is logged to Neptune as an artifact with path html/{name}.html
Args:
name (:obj:`str`):
| Name of the chart (without extension) that will be used as a part of artifact's destination.
html_body (:obj:`str`):
| HTML string that is logged and rendered as HTML.
experiment (:obj:`neptune.experiments.Experiment`, optional, default is ``None``):
| For advanced users only. Pass Neptune
`Experiment <https://docs.neptune.ai/neptune-client/docs/experiment.html#neptune.experiments.Experiment>`_
object if you want to control to which experiment data is logged.
| If ``None``, log to currently active, and most recent experiment.
Examples:
Start an experiment::
import neptune
neptune.init(api_token='ANONYMOUS',
project_qualified_name='shared/showroom')
neptune.create_experiment(name='experiment_with_html')
Create an HTML string::
html = "<button type='button',style='background-color:#005879; width:300px; height:200px; font-size:30px'> \
<a style='color: #ccc', href='https://docs.neptune.ai'> Take me back to the docs!!<a> </button>"
Log it to Neptune::
from neptunecontrib.api import log_html
log_html('go_to_docs_button', html)
Check out how the logged table looks in Neptune:
https://ui.neptune.ai/o/shared/org/showroom/e/SHOW-988/artifacts?path=html%2F&file=button_example.html
"""

_exp = experiment if experiment else neptune

_exp.log_artifact(export_html(html), "htmls/" + name + '.html')


def export_html(html):
from io import StringIO
buffer = StringIO(html)
buffer.seek(0)

return buffer
74 changes: 74 additions & 0 deletions neptunecontrib/api/table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#
# Copyright (c) 2020, Neptune Labs Sp. z o.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import neptune

__all__ = [
'log_table',
]

def log_table(name, table, experiment=None):
"""Logs pandas dataframe to neptune.
Pandas dataframe is converted to an HTML table and logged to Neptune as an artifact with path tables/{name}.html
Args:
name (:obj:`str`):
| Name of the chart (without extension) that will be used as a part of artifact's destination.
table (:obj:`pandas.Dataframe`):
| DataFrame table
experiment (:obj:`neptune.experiments.Experiment`, optional, default is ``None``):
| For advanced users only. Pass Neptune
`Experiment <https://docs.neptune.ai/neptune-client/docs/experiment.html#neptune.experiments.Experiment>`_
object if you want to control to which experiment data is logged.
| If ``None``, log to currently active, and most recent experiment.
Examples:
Start an experiment::
import neptune
neptune.init(api_token='ANONYMOUS',
project_qualified_name='shared/showroom')
neptune.create_experiment(name='experiment_with_tables')
Create or load dataframe::
import pandas as pd
iris_df = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv', nrows=100)
Log it to Neptune::
from neptunecontrib.api import log_table
log_table('pandas_df', iris_df)
Check out how the logged table looks in Neptune:
https://ui.neptune.ai/o/shared/org/showroom/e/SHOW-977/artifacts?path=tables%2F&file=pandas_df.html
"""
_exp = experiment if experiment else neptune

_exp.log_artifact(export_pandas_dataframe(table), "tables/" + name + '.html')


def export_pandas_dataframe(table):
from io import StringIO

buffer = StringIO(table.to_html())
buffer.seek(0)

return buffer
Loading

0 comments on commit 0a9e3a9

Please sign in to comment.