From 7c480a7a986d2e58d3535324d5ba9a12a8e42c36 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Tue, 4 Aug 2015 17:58:02 -0400 Subject: [PATCH 01/18] draft of distplot - Plotly version of seaborn.distplot to add to FigureFactory - plots histogram, curve (kde or normal), and rugplot --- plotly/tools.py | 240 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/plotly/tools.py b/plotly/tools.py index 590166906d6..cfa9d6c7b00 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -2274,6 +2274,99 @@ def create_candlestick(open, high, low, close, return dict(data=data, layout=layout) + @staticmethod + def create_distplot(hist_data, group_labels, + bin_size=1., curve_type='kde', + show_hist=True, show_curve=True, + show_rug=True, **kwargs): + """ + BETA function that creates a distplot similar to seaborn.distplot + + The distplot can be composed of all or any combination of the following + 3 components: (1) histogram, (2) curve: (a) kernal density estimation + or (b)normal curve, and (3)rug plot. Additionally, multiple distplots + (from multiple datasets) can be created in the same plot. + + :param (list) hist_data: list of histogram data, should be a list of + lists if multiple data sets are plotted on the same plot + :param (list) group_labels: list of strings of the name of each data + set + :param (float) bin_size: size of histogram bin, default = 1. + :param (string) curve_type: 'kde' or 'normal', default = 'kde' + :param (bool) show_hist: True/False defines if histogram is added to + distplot, default = True + :param (bool) show_curve: True/False defines if curve is added to + distplot, default = True + :param (bool) show_rug: True/False defines if rug plot is added to + distplot, default = True + :param (list) colors: list of strings of color values, + default = first 10 Plot.ly colors + :param (list) rug_text: list of strings of hovertext for rug_plot, + default=None + + :rtype (dict): returns a representation of a distplot figure. + + Example 1: Simple distplot of 1 data set + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + import scipy + + fig = FF.create_displot() + py.plot(fig, filename='finance/aapl-candlestick', validate=False) + ``` + """ + hist = _Distplot( + hist_data, group_labels, bin_size, + curve_type, **kwargs).make_hist() + if curve_type == 'normal': + curve = _Distplot( + hist_data, group_labels, bin_size, + curve_type, **kwargs).make_normal() + else: + curve = _Distplot( + hist_data, group_labels, bin_size, + curve_type, **kwargs).make_kde() + rug = _Distplot( + hist_data, group_labels, bin_size, + curve_type, **kwargs).make_rug() + + data = [] + if show_hist: + data.append(hist) + if show_curve: + data.append(curve) + if show_rug: + data.append(rug) + layout = graph_objs.Layout( + barmode='overlay', + hovermode='closest', + xaxis1=dict(domain=[0.0, 1.0], + anchor='y2', + zeroline=False), + yaxis1=dict(domain=[0.35, 1], + anchor='free', + position=0.0), + yaxis2=dict(domain=[0, 0.25], + anchor='x1', + dtick=1)) + else: + layout = graph_objs.Layout( + barmode='overlay', + hovermode='closest', + xaxis1=dict(domain=[0.0, 1.0], + anchor='y2', + zeroline=False), + yaxis1=dict(domain=[0., 1], + anchor='free', + position=0.0)) + + data = sum(data, []) + dist_fig = dict(data=data, layout=layout) + + return dist_fig + class _Quiver(FigureFactory): """ @@ -2871,3 +2964,150 @@ def get_candle_decrease(self): return (decrease_x, decrease_close, decrease_dif, stick_decrease_y, stick_decrease_x) + +class _Distplot(FigureFactory): + """ + Refer to TraceFactory.create_distplot() for docstring + """ + def __init__(self, hist_data, group_labels, + bin_size, curve_type, + **kwargs): + self.hist_data = hist_data + self.group_labels = group_labels + self.bin_size = bin_size + if 'rug_text' in kwargs: + self.rug_text = rug_text + else: + self.rug_text = None + self.trace_number = len(hist_data) + + self.start = [] + self.end = [] + if 'colors' in kwargs: + self.colors = colors + else: + self.colors = colors = [ + "rgb(31, 119, 180)", "rgb(255, 127, 14)", + "rgb(44, 160, 44)", "rgb(214, 39, 40)", + "rgb(148, 103, 189)", "rgb(140, 86, 75)", + "rgb(227, 119, 194)", "rgb(127, 127, 127)", + "rgb(188, 189, 34)", "rgb(23, 190, 207)"] + self.curve_x = [None]*self.trace_number + self.curve_y = [None]*self.trace_number + + for trace in self.hist_data: + self.start.append(min(trace)*1.) + self.end.append(max(trace)*1.) + + def make_hist(self): + """ + Makes the histogram(s) for FigureFactory.create_distplot(). + + :rtype (list) hist: list of histogram representations + """ + hist = [None]*self.trace_number + + for index in range(self.trace_number): + hist[index] = dict(type='histogram', + x=self.hist_data[index], + xaxis='x1', + yaxis='y1', + histnorm='probability', + name=self.group_labels[index], + legendgroup=self.group_labels[index], + marker=dict(color=self.colors[index]), + autobinx=False, + xbins=dict(start=self.start[index], + end=self.end[index], + size=self.bin_size), + opacity=.7) + return hist + + def make_kde(self): + """ + Makes the kernal density estimation(s) for create_distplot(). + + This is called when curve_type = 'kde' in create_distplot(). + + :rtype (list) curve: list of kde representations + """ + curve = [None]*self.trace_number + for index in range(self.trace_number): + self.curve_x[index] = [self.start[index] + + x * (self.end[index] - self.start[index]) + / 500 for x in range(500)] + self.curve_y[index] = (gaussian_kde(self.hist_data[index]) + (self.curve_x[index])) + self.curve_y[index] *= self.bin_size + + for index in range(self.trace_number): + curve[index] = dict(type='scatter', + x=self.curve_x[index], + y=self.curve_y[index], + xaxis='x1', + yaxis='y1', + mode='lines', + name=self.group_labels[index], + legendgroup=self.group_labels[index], + showlegend=False, + marker=dict(color=self.colors[index])) + return curve + + def make_normal(self): + """ + Makes the normal curve(s) for create_distplot(). + + This is called when curve_type = 'normal' in create_distplot(). + + :rtype (list) curve: list of normal curve representations + """ + curve = [None]*self.trace_number + mean = [None]*self.trace_number + sd = [None]*self.trace_number + + for index in range(self.trace_number): + mean[index], sd[index] = norm.fit(self.hist_data[index]) + self.curve_x[index] = [self.start[index] + + x * (self.end[index] - self.start[index]) + / 500 for x in range(500)] + self.curve_y[index] = norm.pdf( + self.curve_x[index], loc=mean[index], scale=sd[index]) + self.curve_y[index] *= self.bin_size + + for index in range(self.trace_number): + curve[index] = dict(type='scatter', + x=self.curve_x[index], + y=self.curve_y[index], + xaxis='x1', + yaxis='y1', + mode='lines', + name=self.group_labels[index], + legendgroup=self.group_labels[index], + showlegend=False, + marker=dict(color=self.colors[index])) + return curve + + def make_rug(self): + """ + Makes the rug plot(s) for FigureFactory.create_distplot(). + + :rtype (list) rug: list of rug plot representations + """ + rug = [None]*self.trace_number + for index in range(self.trace_number): + + rug[index] = dict(type='scatter', + x=self.hist_data[index], + y=([self.group_labels[index]] * + len(self.hist_data[index])), + xaxis='x1', + yaxis='y2', + mode='markers', + name=self.group_labels[index], + legendgroup=self.group_labels[index], + showlegend=False, + text=self.rug_text, + marker=dict(color=self.colors[index], + symbol='line-ns-open')) + return rug + From 2b84b1e0eec687c64cca6e7ccbb51af8a03f8aa6 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 10:55:36 -0400 Subject: [PATCH 02/18] add displot validations - add `validate_displot()` method to tools.py - add core tests for distplot parameters --- .../test_tools/test_figure_factory.py | 24 +++++ plotly/tools.py | 97 ++++++++++++++++++- 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index e4852ac816e..59e0f9dca23 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -9,6 +9,30 @@ from plotly.graph_objs import graph_objs +class TestDistplot(TestCase): + + def test_wrong_curve_type(self): + kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], + 'curve_type': 'curve'} + self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " + "'kde' or 'normal'", + tls.FigureFactory.create_distplot, **kwargs) + + def test_wrong_histdata_format(self): + kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + def test_unequal_data_label_length(self): + kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + class TestQuiver(TestCase): def test_unequal_xy_length(self): diff --git a/plotly/tools.py b/plotly/tools.py index cfa9d6c7b00..ad8522fd8a5 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1500,6 +1500,28 @@ def validate_ohlc(open, high, low, close, direction, **kwargs): "'increasing', 'decreasing', or " "'both'") + @staticmethod + def validate_distplot(hist_data, curve_type): + """ + distplot specific validations + + :raises: (PlotlyError) If hist_data is not a list of lists + :raises: (PlotlyError) If curve_type is not valid (i.e. not 'kde' or + 'normal'). + """ + if type(hist_data[0]) not in [list, np.ndarray]: + raise exceptions.PlotlyError("Oops, this function was written to " + "handle multiple datasets, if you " + "want to plot just one, make sure " + "your hist_data variable is still " + "a list of lists, i.e. x = " + "[1, 2, 3] -> x = [[1, 2, 3]]") + + curve_opts = ('kde', 'normal') + if curve_type not in curve_opts: + raise exceptions.PlotlyError("curve_type must be defined as " + "'kde' or 'normal'") + @staticmethod def validate_positive_scalars(**kwargs): """ @@ -2311,12 +2333,79 @@ def create_distplot(hist_data, group_labels, import plotly.plotly as py from plotly.tools import FigureFactory as FF - import scipy + from scipy.stats import norm, gaussian_kde - fig = FF.create_displot() - py.plot(fig, filename='finance/aapl-candlestick', validate=False) + hist_x = [[1.1, 1.1, 2.5, 3.0, 3.5, + 3.5, 4.1, 4.4, 4.5, 4.5, + 5.0, 5.0, 5.2, 5.5, 5.5, + 5.5, 5.5, 5.5, 6.1, 7.0]] + + group_label = ['distplot example'] + + fig = FF.create_distplot(hist_x, group_label) + + url = py.plot(fig, filename='Simple distplot', validate=False) + ``` + + Example 2: Two data sets and added rug text ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + from scipy.stats import norm, gaussian_kde + + # Add histogram data + hist1_x = [0.8, 1.2, 0.2, 0.6, 1.6, + -0.9, -0.07, 1.95, 0.9, -0.2, + -0.5, 0.3, 0.4, -0.37, 0.6] + hist2_x = [0.8, 1.5, 1.5, 0.6, 0.59, + 1.0, 0.8, 1.7, 0.5, 0.8, + -0.3, 1.2, 0.56, 0.3, 2.2] + + # Group data together + all_hist_data = [hist1_x] + [hist2_x] + + group_labels = ['2012', '2013'] + + rug_text = ['a', 'b', 'c', 'd', 'e', + 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o'] + + # Create distplot + fig = FF.create_distplot( + all_hist_data, group_labels, rug_text=rug_text, bin_size=.2) + + # Add title + fig['layout'].update(title='Dist Plot') + + # Plot! + py.iplot(fig, filename='Distplot with rug text', validate=False) + ``` + + Example 3: Plot with normal curve and hide rug plot + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + from scipy.stats import norm, gaussian_kde + + x1 = np.random.randn(20) + x2 = np.random.randn(40)+1 + x3 = np.random.randn(10)-1 + x4 = np.random.randn(10)+2 + + all_hist_data = [x1] + [x2] + [x3] + [x4] + hist_labels = ['2012', '2013', '2014', '2015'] + + fig = FF.create_distplot( + all_hist_data, hist_labels, curve_type='normal', + show_rug=False, bin_size=.4) + + url = py.plot(fig, filename='hist and normal curve', validate=False) """ + FigureFactory.validate_distplot(hist_data, curve_type) + FigureFactory.validate_equal_length(hist_data, group_labels) + hist = _Distplot( hist_data, group_labels, bin_size, curve_type, **kwargs).make_hist() @@ -3089,7 +3178,7 @@ def make_normal(self): def make_rug(self): """ - Makes the rug plot(s) for FigureFactory.create_distplot(). + Makes the rug plot(s) for create_distplot(). :rtype (list) rug: list of rug plot representations """ From be77804ccb1f00ba9fcdd49380b62e6f1c58452e Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 13:27:20 -0400 Subject: [PATCH 03/18] edits re scipy - add protected import of scipy to tools.py - add distplot checks to tests_optional --- .../test_tools/test_figure_factory.py | 9 ++ .../test_optional/test_opt_tracefactory.py | 113 ++++++++++++++++++ plotly/tools.py | 33 +++-- 3 files changed, 147 insertions(+), 8 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 59e0f9dca23..88c5c5e7976 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -12,6 +12,10 @@ class TestDistplot(TestCase): def test_wrong_curve_type(self): + + # check: PlotlyError (and specific message) is raised if curve_type is + # not 'kde' or 'normal' + kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], 'curve_type': 'curve'} self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " @@ -19,6 +23,11 @@ def test_wrong_curve_type(self): tls.FigureFactory.create_distplot, **kwargs) def test_wrong_histdata_format(self): + + # check: PlotlyError if hist_data is not a list of lists or list of + # np.ndarrays (if hist_data is entered as just a list the function + # will fail) + kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, **kwargs) diff --git a/plotly/tests/test_optional/test_opt_tracefactory.py b/plotly/tests/test_optional/test_opt_tracefactory.py index 2c87c20c232..47697464bb0 100644 --- a/plotly/tests/test_optional/test_opt_tracefactory.py +++ b/plotly/tests/test_optional/test_opt_tracefactory.py @@ -7,6 +7,119 @@ from nose.tools import raises import numpy as np +from scipy.stats import norm, gaussian_kde + + +class TestDistplot(TestCase): + + def test_simple_distplot(self): + + # we should be able to create a single distplot with a simple dataset + # and default kwargs + + dp = tls.FigureFactory.create_distplot(hist_data=[[1, 2, 2, 3]], + group_labels=['distplot']) + expected_dp_layout = {'barmode': 'overlay', + 'hovermode': 'closest', + 'xaxis1': {'anchor': 'y2', + 'domain': [0.0, 1.0], + 'zeroline': False}, + 'yaxis1': {'anchor': 'free', + 'domain': [0.35, 1], + 'position': 0.0}, + 'yaxis2': {'anchor': 'x1', + 'domain': [0, 0.25], + 'dtick': 1}} + self.assertEqual(dp['layout'], expected_dp_layout) + + expected_dp_data_hist = {'autobinx': False, + 'histnorm': 'probability', + 'legendgroup': 'distplot', + 'marker': {'color': 'rgb(31, 119, 180)'}, + 'name': 'distplot', + 'opacity': 0.7, + 'type': 'histogram', + 'x': [1, 2, 2, 3], + 'xaxis': 'x1', + 'xbins': {'end': 3.0, 'size': 1.0, + 'start': 1.0}, + 'yaxis': 'y1'} + self.assertEqual(dp['data'][0], expected_dp_data_hist) + + expected_dp_data_rug = {'legendgroup': 'distplot', + 'marker': {'color': 'rgb(31, 119, 180)', + 'symbol': 'line-ns-open'}, + 'mode': 'markers', + 'name': 'distplot', + 'showlegend': False, + 'text': None, + 'type': 'scatter', + 'x': [1, 2, 2, 3], + 'xaxis': 'x1', + 'y': ['distplot', 'distplot', 'distplot', + 'distplot'], + 'yaxis': 'y2'} + self.assertEqual(dp['data'][2], expected_dp_data_rug) + + def test_distplot_more_args(self): + + # we should be able to create a distplot with 2 datasets no + # rugplot, defined bin_size, and added title + + hist1_x = [0.8, 1.2, 0.2, 0.6, 1.6, + -0.9, -0.07, 1.95, 0.9, -0.2, + -0.5, 0.3, 0.4, -0.37, 0.6] + hist2_x = [0.8, 1.5, 1.5, 0.6, 0.59, + 1.0, 0.8, 1.7, 0.5, 0.8, + -0.3, 1.2, 0.56, 0.3, 2.2] + + hist_data = [hist1_x] + [hist2_x] + group_labels = ['2012', '2013'] + + dp = tls.FigureFactory.create_distplot(hist_data, group_labels, + show_rug=False, bin_size=.2) + dp['layout'].update(title='Dist Plot') + + expected_dp_layout = {'barmode': 'overlay', + 'hovermode': 'closest', + 'title': 'Dist Plot', + 'xaxis1': {'anchor': 'y2', 'domain': [0.0, 1.0], + 'zeroline': False}, + 'yaxis1': {'anchor': 'free', 'domain': [0.0, 1], + 'position': 0.0}} + self.assertEqual(dp['layout'], expected_dp_layout) + + expected_dp_data_hist_1 = {'autobinx': False, + 'histnorm': 'probability', + 'legendgroup': '2012', + 'marker': {'color': 'rgb(31, 119, 180)'}, + 'name': '2012', + 'opacity': 0.7, + 'type': 'histogram', + 'x': [0.8, 1.2, 0.2, 0.6, 1.6, -0.9, -0.07, + 1.95, 0.9, -0.2, -0.5, 0.3, 0.4, + -0.37, 0.6], + 'xaxis': 'x1', + 'xbins': {'end': 1.95, 'size': 0.2, + 'start': -0.9}, + 'yaxis': 'y1'} + self.assertEqual(dp['data'][0], expected_dp_data_hist_1) + + expected_dp_data_hist_2 = {'autobinx': False, + 'histnorm': 'probability', + 'legendgroup': '2013', + 'marker': {'color': 'rgb(255, 127, 14)'}, + 'name': '2013', + 'opacity': 0.7, + 'type': 'histogram', + 'x': [0.8, 1.5, 1.5, 0.6, 0.59, 1.0, 0.8, + 1.7, 0.5, 0.8, -0.3, 1.2, 0.56, 0.3, + 2.2], + 'xaxis': 'x1', + 'xbins': {'end': 2.2, 'size': 0.2, + 'start': -0.3}, + 'yaxis': 'y1'} + self.assertEqual(dp['data'][1], expected_dp_data_hist_2) class TestStreamline(TestCase): diff --git a/plotly/tools.py b/plotly/tools.py index ad8522fd8a5..3a4b856a2de 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -51,6 +51,12 @@ def warning_on_one_line(message, category, filename, lineno, except ImportError: _numpy_imported = False +try: + from scipy.stats import norm, gaussian_kde + _scipy_imported = True +except ImportError: + _scipy_imported = False + PLOTLY_DIR = os.path.join(os.path.expanduser("~"), ".plotly") CREDENTIALS_FILE = os.path.join(PLOTLY_DIR, ".credentials") CONFIG_FILE = os.path.join(PLOTLY_DIR, ".config") @@ -1509,12 +1515,20 @@ def validate_distplot(hist_data, curve_type): :raises: (PlotlyError) If curve_type is not valid (i.e. not 'kde' or 'normal'). """ - if type(hist_data[0]) not in [list, np.ndarray]: - raise exceptions.PlotlyError("Oops, this function was written to " - "handle multiple datasets, if you " - "want to plot just one, make sure " - "your hist_data variable is still " - "a list of lists, i.e. x = " + if _numpy_imported is True: + if type(hist_data[0]) not in [list, np.ndarray]: + raise exceptions.PlotlyError("Oops, this function was written " + "to handle multiple datasets, if " + "you want to plot just one, make " + "sure your hist_data variable is " + "still a list of lists, i.e. x = " + "[1, 2, 3] -> x = [[1, 2, 3]]") + elif type(hist_data[0]) is not list: + raise exceptions.PlotlyError("Oops, this function was written " + "to handle multiple datasets, if " + "you want to plot just one, make " + "sure your hist_data variable is " + "still a list of lists, i.e. x = " "[1, 2, 3] -> x = [[1, 2, 3]]") curve_opts = ('kde', 'normal') @@ -1522,6 +1536,9 @@ def validate_distplot(hist_data, curve_type): raise exceptions.PlotlyError("curve_type must be defined as " "'kde' or 'normal'") + if _scipy_imported is False: + raise ImportError("FigureFactory.create_distplot requires scipy") + @staticmethod def validate_positive_scalars(**kwargs): """ @@ -2395,10 +2412,10 @@ def create_distplot(hist_data, group_labels, x4 = np.random.randn(10)+2 all_hist_data = [x1] + [x2] + [x3] + [x4] - hist_labels = ['2012', '2013', '2014', '2015'] + group_labels = ['2012', '2013', '2014', '2015'] fig = FF.create_distplot( - all_hist_data, hist_labels, curve_type='normal', + all_hist_data, group_labels, curve_type='normal', show_rug=False, bin_size=.4) url = py.plot(fig, filename='hist and normal curve', validate=False) From d88e0f2e81bbae07cf77f09cf9cbbcab22316311 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 14:03:10 -0400 Subject: [PATCH 04/18] moved scipy dependent tests from core to optional --- .../test_tools/test_figure_factory.py | 33 ------------------- .../test_optional/test_opt_tracefactory.py | 30 +++++++++++++++++ 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 88c5c5e7976..e4852ac816e 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -9,39 +9,6 @@ from plotly.graph_objs import graph_objs -class TestDistplot(TestCase): - - def test_wrong_curve_type(self): - - # check: PlotlyError (and specific message) is raised if curve_type is - # not 'kde' or 'normal' - - kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], - 'curve_type': 'curve'} - self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " - "'kde' or 'normal'", - tls.FigureFactory.create_distplot, **kwargs) - - def test_wrong_histdata_format(self): - - # check: PlotlyError if hist_data is not a list of lists or list of - # np.ndarrays (if hist_data is entered as just a list the function - # will fail) - - kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - def test_unequal_data_label_length(self): - kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - class TestQuiver(TestCase): def test_unequal_xy_length(self): diff --git a/plotly/tests/test_optional/test_opt_tracefactory.py b/plotly/tests/test_optional/test_opt_tracefactory.py index 47697464bb0..74a89ef74cb 100644 --- a/plotly/tests/test_optional/test_opt_tracefactory.py +++ b/plotly/tests/test_optional/test_opt_tracefactory.py @@ -12,6 +12,36 @@ class TestDistplot(TestCase): + def test_wrong_curve_type(self): + + # check: PlotlyError (and specific message) is raised if curve_type is + # not 'kde' or 'normal' + + kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], + 'curve_type': 'curve'} + self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " + "'kde' or 'normal'", + tls.FigureFactory.create_distplot, **kwargs) + + def test_wrong_histdata_format(self): + + # check: PlotlyError if hist_data is not a list of lists or list of + # np.ndarrays (if hist_data is entered as just a list the function + # will fail) + + kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + def test_unequal_data_label_length(self): + kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + def test_simple_distplot(self): # we should be able to create a single distplot with a simple dataset From 8bb46eb71f212a9e6840e8f74c4a45264bec3dc7 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 15:19:23 -0400 Subject: [PATCH 05/18] edit scipy protected import --- plotly/tools.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 3a4b856a2de..20ce039a4eb 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -52,6 +52,7 @@ def warning_on_one_line(message, category, filename, lineno, _numpy_imported = False try: + import scipy from scipy.stats import norm, gaussian_kde _scipy_imported = True except ImportError: @@ -2350,7 +2351,7 @@ def create_distplot(hist_data, group_labels, import plotly.plotly as py from plotly.tools import FigureFactory as FF - from scipy.stats import norm, gaussian_kde + hist_x = [[1.1, 1.1, 2.5, 3.0, 3.5, 3.5, 4.1, 4.4, 4.5, 4.5, @@ -2369,8 +2370,6 @@ def create_distplot(hist_data, group_labels, import plotly.plotly as py from plotly.tools import FigureFactory as FF - from scipy.stats import norm, gaussian_kde - # Add histogram data hist1_x = [0.8, 1.2, 0.2, 0.6, 1.6, -0.9, -0.07, 1.95, 0.9, -0.2, @@ -2404,8 +2403,6 @@ def create_distplot(hist_data, group_labels, import plotly.plotly as py from plotly.tools import FigureFactory as FF - from scipy.stats import norm, gaussian_kde - x1 = np.random.randn(20) x2 = np.random.randn(40)+1 x3 = np.random.randn(10)-1 From f5240eb52ef1f18df92f8988bec0e25955debe50 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 16:15:12 -0400 Subject: [PATCH 06/18] add scipy.stats to protected import - add scipy.stats - edit examples in docstring --- plotly/tools.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 20ce039a4eb..5188087e1bd 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -53,7 +53,7 @@ def warning_on_one_line(message, category, filename, lineno, try: import scipy - from scipy.stats import norm, gaussian_kde + import scipy.stats _scipy_imported = True except ImportError: _scipy_imported = False @@ -2351,16 +2351,14 @@ def create_distplot(hist_data, group_labels, import plotly.plotly as py from plotly.tools import FigureFactory as FF + hist_data = [[1.1, 1.1, 2.5, 3.0, 3.5, + 3.5, 4.1, 4.4, 4.5, 4.5, + 5.0, 5.0, 5.2, 5.5, 5.5, + 5.5, 5.5, 5.5, 6.1, 7.0]] + group_labels = ['distplot example'] - hist_x = [[1.1, 1.1, 2.5, 3.0, 3.5, - 3.5, 4.1, 4.4, 4.5, 4.5, - 5.0, 5.0, 5.2, 5.5, 5.5, - 5.5, 5.5, 5.5, 6.1, 7.0]] - - group_label = ['distplot example'] - - fig = FF.create_distplot(hist_x, group_label) + fig = FF.create_distplot(hist_data, group_labels) url = py.plot(fig, filename='Simple distplot', validate=False) ``` @@ -2379,7 +2377,7 @@ def create_distplot(hist_data, group_labels, -0.3, 1.2, 0.56, 0.3, 2.2] # Group data together - all_hist_data = [hist1_x] + [hist2_x] + hist_data = [hist1_x] + [hist2_x] group_labels = ['2012', '2013'] @@ -2389,13 +2387,13 @@ def create_distplot(hist_data, group_labels, # Create distplot fig = FF.create_distplot( - all_hist_data, group_labels, rug_text=rug_text, bin_size=.2) + hist_data, group_labels, rug_text=rug_text, bin_size=.2) # Add title fig['layout'].update(title='Dist Plot') # Plot! - py.iplot(fig, filename='Distplot with rug text', validate=False) + url = py.plot(fig, filename='Distplot with rug text', validate=False) ``` Example 3: Plot with normal curve and hide rug plot @@ -2408,11 +2406,11 @@ def create_distplot(hist_data, group_labels, x3 = np.random.randn(10)-1 x4 = np.random.randn(10)+2 - all_hist_data = [x1] + [x2] + [x3] + [x4] + hist_data = [x1] + [x2] + [x3] + [x4] group_labels = ['2012', '2013', '2014', '2015'] fig = FF.create_distplot( - all_hist_data, group_labels, curve_type='normal', + hist_data, group_labels, curve_type='normal', show_rug=False, bin_size=.4) url = py.plot(fig, filename='hist and normal curve', validate=False) @@ -3139,7 +3137,8 @@ def make_kde(self): self.curve_x[index] = [self.start[index] + x * (self.end[index] - self.start[index]) / 500 for x in range(500)] - self.curve_y[index] = (gaussian_kde(self.hist_data[index]) + self.curve_y[index] = (scipy.stats.gaussian_kde + (self.hist_data[index]) (self.curve_x[index])) self.curve_y[index] *= self.bin_size @@ -3169,11 +3168,12 @@ def make_normal(self): sd = [None]*self.trace_number for index in range(self.trace_number): - mean[index], sd[index] = norm.fit(self.hist_data[index]) + mean[index], sd[index] = (scipy.stats.norm.fit + (self.hist_data[index])) self.curve_x[index] = [self.start[index] + x * (self.end[index] - self.start[index]) / 500 for x in range(500)] - self.curve_y[index] = norm.pdf( + self.curve_y[index] = scipy.stats.norm.pdf( self.curve_x[index], loc=mean[index], scale=sd[index]) self.curve_y[index] *= self.bin_size From e42634b5ad27b59bbc45d5a0b8bab95c43a84e62 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 16:27:32 -0400 Subject: [PATCH 07/18] edit scipy import in optional tests --- plotly/tests/test_optional/test_opt_tracefactory.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plotly/tests/test_optional/test_opt_tracefactory.py b/plotly/tests/test_optional/test_opt_tracefactory.py index 74a89ef74cb..69f3070f8c3 100644 --- a/plotly/tests/test_optional/test_opt_tracefactory.py +++ b/plotly/tests/test_optional/test_opt_tracefactory.py @@ -7,7 +7,9 @@ from nose.tools import raises import numpy as np -from scipy.stats import norm, gaussian_kde + +import scipy +import scipy.stats class TestDistplot(TestCase): From 4f442e297142037b5408ca307c37f1084f9378d6 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 16:46:05 -0400 Subject: [PATCH 08/18] move non-scipy dependent checks to test_core --- .../test_tools/test_figure_factory.py | 34 +++++++++++++++++++ .../test_optional/test_opt_tracefactory.py | 33 ------------------ 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index e4852ac816e..0021fdc12f3 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -802,3 +802,37 @@ def test_datetime_candlestick(self): self.assertEqual(candle, exp_candle) + +class TestDistplot(TestCase): + + def test_wrong_curve_type(self): + + # check: PlotlyError (and specific message) is raised if curve_type is + # not 'kde' or 'normal' + + kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], + 'curve_type': 'curve'} + self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " + "'kde' or 'normal'", + tls.FigureFactory.create_distplot, **kwargs) + + def test_wrong_histdata_format(self): + + # check: PlotlyError if hist_data is not a list of lists or list of + # np.ndarrays (if hist_data is entered as just a list the function + # will fail) + + kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + def test_unequal_data_label_length(self): + kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + \ No newline at end of file diff --git a/plotly/tests/test_optional/test_opt_tracefactory.py b/plotly/tests/test_optional/test_opt_tracefactory.py index 69f3070f8c3..e84e3591ffb 100644 --- a/plotly/tests/test_optional/test_opt_tracefactory.py +++ b/plotly/tests/test_optional/test_opt_tracefactory.py @@ -8,42 +8,9 @@ import numpy as np -import scipy -import scipy.stats - class TestDistplot(TestCase): - def test_wrong_curve_type(self): - - # check: PlotlyError (and specific message) is raised if curve_type is - # not 'kde' or 'normal' - - kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], - 'curve_type': 'curve'} - self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " - "'kde' or 'normal'", - tls.FigureFactory.create_distplot, **kwargs) - - def test_wrong_histdata_format(self): - - # check: PlotlyError if hist_data is not a list of lists or list of - # np.ndarrays (if hist_data is entered as just a list the function - # will fail) - - kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - def test_unequal_data_label_length(self): - kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - def test_simple_distplot(self): # we should be able to create a single distplot with a simple dataset From 0f2d92ad49cecf4b8618a605f1d2b43fe5847b7e Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 5 Aug 2015 17:20:59 -0400 Subject: [PATCH 09/18] move distplot tests - all distplot tests are depending on scipy --- .../test_tools/test_figure_factory.py | 34 ------------------- .../test_optional/test_opt_tracefactory.py | 30 ++++++++++++++++ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 0021fdc12f3..e4852ac816e 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -802,37 +802,3 @@ def test_datetime_candlestick(self): self.assertEqual(candle, exp_candle) - -class TestDistplot(TestCase): - - def test_wrong_curve_type(self): - - # check: PlotlyError (and specific message) is raised if curve_type is - # not 'kde' or 'normal' - - kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], - 'curve_type': 'curve'} - self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " - "'kde' or 'normal'", - tls.FigureFactory.create_distplot, **kwargs) - - def test_wrong_histdata_format(self): - - # check: PlotlyError if hist_data is not a list of lists or list of - # np.ndarrays (if hist_data is entered as just a list the function - # will fail) - - kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - def test_unequal_data_label_length(self): - kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} - self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, - **kwargs) - - \ No newline at end of file diff --git a/plotly/tests/test_optional/test_opt_tracefactory.py b/plotly/tests/test_optional/test_opt_tracefactory.py index e84e3591ffb..857b1fedfaa 100644 --- a/plotly/tests/test_optional/test_opt_tracefactory.py +++ b/plotly/tests/test_optional/test_opt_tracefactory.py @@ -11,6 +11,36 @@ class TestDistplot(TestCase): + def test_wrong_curve_type(self): + + # check: PlotlyError (and specific message) is raised if curve_type is + # not 'kde' or 'normal' + + kwargs = {'hist_data': [[1, 2, 3]], 'group_labels': ['group'], + 'curve_type': 'curve'} + self.assertRaisesRegexp(PlotlyError, "curve_type must be defined as " + "'kde' or 'normal'", + tls.FigureFactory.create_distplot, **kwargs) + + def test_wrong_histdata_format(self): + + # check: PlotlyError if hist_data is not a list of lists or list of + # np.ndarrays (if hist_data is entered as just a list the function + # will fail) + + kwargs = {'hist_data': [1, 2, 3], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + def test_unequal_data_label_length(self): + kwargs = {'hist_data': [[1, 2]], 'group_labels': ['group', 'group2']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + + kwargs = {'hist_data': [[1, 2], [1, 2, 3]], 'group_labels': ['group']} + self.assertRaises(PlotlyError, tls.FigureFactory.create_distplot, + **kwargs) + def test_simple_distplot(self): # we should be able to create a single distplot with a simple dataset From dfb59e7ca6bcea76fa1308e2b79d751dde64fc9d Mon Sep 17 00:00:00 2001 From: Chelsea Date: Thu, 6 Aug 2015 13:25:08 -0400 Subject: [PATCH 10/18] add scipy to optional-requirements.txt --- optional-requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/optional-requirements.txt b/optional-requirements.txt index ea95c4fc9fb..2f609e087d0 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -19,3 +19,6 @@ ipython[all] ## pandas deps for some matplotlib functionality ## pandas + +## scipy deps for some FigureFactory functions ## +scipy From 7dd20f4c4735b700c3f3083f505507af6ae073b5 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Fri, 7 Aug 2015 13:10:03 -0400 Subject: [PATCH 11/18] distplot edits - DRYing type validation - fix over-indentation --- plotly/tools.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 5188087e1bd..90f351b4b17 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1516,21 +1516,17 @@ def validate_distplot(hist_data, curve_type): :raises: (PlotlyError) If curve_type is not valid (i.e. not 'kde' or 'normal'). """ - if _numpy_imported is True: - if type(hist_data[0]) not in [list, np.ndarray]: + hist_data_types = (list,) + if _numpy_imported: + hist_data_types += (np.ndarray,) + + if not isinstance(hist_data[0], hist_data_types): raise exceptions.PlotlyError("Oops, this function was written " "to handle multiple datasets, if " "you want to plot just one, make " "sure your hist_data variable is " "still a list of lists, i.e. x = " "[1, 2, 3] -> x = [[1, 2, 3]]") - elif type(hist_data[0]) is not list: - raise exceptions.PlotlyError("Oops, this function was written " - "to handle multiple datasets, if " - "you want to plot just one, make " - "sure your hist_data variable is " - "still a list of lists, i.e. x = " - "[1, 2, 3] -> x = [[1, 2, 3]]") curve_opts = ('kde', 'normal') if curve_type not in curve_opts: @@ -2453,15 +2449,15 @@ def create_distplot(hist_data, group_labels, anchor='x1', dtick=1)) else: - layout = graph_objs.Layout( - barmode='overlay', - hovermode='closest', - xaxis1=dict(domain=[0.0, 1.0], - anchor='y2', - zeroline=False), - yaxis1=dict(domain=[0., 1], - anchor='free', - position=0.0)) + layout = graph_objs.Layout( + barmode='overlay', + hovermode='closest', + xaxis1=dict(domain=[0.0, 1.0], + anchor='y2', + zeroline=False), + yaxis1=dict(domain=[0., 1], + anchor='free', + position=0.0)) data = sum(data, []) dist_fig = dict(data=data, layout=layout) From 92458ea926f7fcff4e39b16d78654699ecf0e2a3 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Fri, 7 Aug 2015 15:26:30 -0400 Subject: [PATCH 12/18] fix setting colors --- plotly/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 90f351b4b17..ce061c2f950 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3081,9 +3081,9 @@ def __init__(self, hist_data, group_labels, self.start = [] self.end = [] if 'colors' in kwargs: - self.colors = colors + self.colors = kwargs['colors'] else: - self.colors = colors = [ + self.colors = [ "rgb(31, 119, 180)", "rgb(255, 127, 14)", "rgb(44, 160, 44)", "rgb(214, 39, 40)", "rgb(148, 103, 189)", "rgb(140, 86, 75)", From 2c5ab6c90e573a5c141f149944f8c29113dc0477 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Mon, 24 Aug 2015 15:56:42 -0400 Subject: [PATCH 13/18] pep8 operator spaces --- plotly/tools.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index ce061c2f950..55671f95754 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3089,12 +3089,12 @@ def __init__(self, hist_data, group_labels, "rgb(148, 103, 189)", "rgb(140, 86, 75)", "rgb(227, 119, 194)", "rgb(127, 127, 127)", "rgb(188, 189, 34)", "rgb(23, 190, 207)"] - self.curve_x = [None]*self.trace_number - self.curve_y = [None]*self.trace_number + self.curve_x = [None] * self.trace_number + self.curve_y = [None] * self.trace_number for trace in self.hist_data: - self.start.append(min(trace)*1.) - self.end.append(max(trace)*1.) + self.start.append(min(trace) * 1.) + self.end.append(max(trace) * 1.) def make_hist(self): """ @@ -3102,7 +3102,7 @@ def make_hist(self): :rtype (list) hist: list of histogram representations """ - hist = [None]*self.trace_number + hist = [None] * self.trace_number for index in range(self.trace_number): hist[index] = dict(type='histogram', @@ -3128,7 +3128,7 @@ def make_kde(self): :rtype (list) curve: list of kde representations """ - curve = [None]*self.trace_number + curve = [None] * self.trace_number for index in range(self.trace_number): self.curve_x[index] = [self.start[index] + x * (self.end[index] - self.start[index]) @@ -3159,9 +3159,9 @@ def make_normal(self): :rtype (list) curve: list of normal curve representations """ - curve = [None]*self.trace_number - mean = [None]*self.trace_number - sd = [None]*self.trace_number + curve = [None] * self.trace_number + mean = [None] * self.trace_number + sd = [None] * self.trace_number for index in range(self.trace_number): mean[index], sd[index] = (scipy.stats.norm.fit @@ -3192,7 +3192,7 @@ def make_rug(self): :rtype (list) rug: list of rug plot representations """ - rug = [None]*self.trace_number + rug = [None] * self.trace_number for index in range(self.trace_number): rug[index] = dict(type='scatter', From 18ad118067b3597ccca546eb102a24ca914a23c1 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Fri, 4 Sep 2015 15:25:42 -0400 Subject: [PATCH 14/18] distplot_updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hide validate functions from tab-tab helper - add pandas example and pandas type to validation - replace `**kwargs` with `rug_text` and `colors` in - updated the docstring with Andrew’s nice terse suggestions - hid tick label from rug plot - modified rug_text so its indexed across datasets, not copied - edited examples - added import numpy where needed - increased sample sizes - reversed legend - fix the show_legend issue so a legend is shown if the hist is hidden - add test to test_core to check exception for `scipy_import = False` - edit tests in test optional to reflect the changes (mentioned above) made --- .../test_tools/test_figure_factory.py | 19 ++ .../test_optional/test_opt_tracefactory.py | 26 ++- plotly/tools.py | 201 +++++++++++------- 3 files changed, 151 insertions(+), 95 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index e4852ac816e..d029abbbada 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -802,3 +802,22 @@ def test_datetime_candlestick(self): self.assertEqual(candle, exp_candle) + +class TestDistplot(TestCase): + + def test_scipy_import_error(self): + + # make sure Import Error is raised when _scipy_imported = False + + hist_data = [[1.1, 1.1, 2.5, 3.0, 3.5, + 3.5, 4.1, 4.4, 4.5, 4.5, + 5.0, 5.0, 5.2, 5.5, 5.5, + 5.5, 5.5, 5.5, 6.1, 7.0]] + + group_labels = ['distplot example'] + + self.assertRaisesRegexp(ImportError, + "FigureFactory.create_distplot requires scipy", + tls.FigureFactory.create_distplot, + hist_data, group_labels) + diff --git a/plotly/tests/test_optional/test_opt_tracefactory.py b/plotly/tests/test_optional/test_opt_tracefactory.py index 857b1fedfaa..b6de29eb8e0 100644 --- a/plotly/tests/test_optional/test_opt_tracefactory.py +++ b/plotly/tests/test_optional/test_opt_tracefactory.py @@ -49,16 +49,14 @@ def test_simple_distplot(self): dp = tls.FigureFactory.create_distplot(hist_data=[[1, 2, 2, 3]], group_labels=['distplot']) expected_dp_layout = {'barmode': 'overlay', - 'hovermode': 'closest', - 'xaxis1': {'anchor': 'y2', - 'domain': [0.0, 1.0], - 'zeroline': False}, - 'yaxis1': {'anchor': 'free', - 'domain': [0.35, 1], - 'position': 0.0}, - 'yaxis2': {'anchor': 'x1', - 'domain': [0, 0.25], - 'dtick': 1}} + 'hovermode': 'closest', + 'legend': {'traceorder': 'reversed'}, + 'xaxis1': {'anchor': 'y2', 'domain': [0.0, 1.0], 'zeroline': False}, + 'yaxis1': {'anchor': 'free', 'domain': [0.35, 1], 'position': 0.0}, + 'yaxis2': {'anchor': 'x1', + 'domain': [0, 0.25], + 'dtick': 1, + 'showticklabels': False}} self.assertEqual(dp['layout'], expected_dp_layout) expected_dp_data_hist = {'autobinx': False, @@ -70,8 +68,7 @@ def test_simple_distplot(self): 'type': 'histogram', 'x': [1, 2, 2, 3], 'xaxis': 'x1', - 'xbins': {'end': 3.0, 'size': 1.0, - 'start': 1.0}, + 'xbins': {'end': 3.0, 'size': 1.0, 'start': 1.0}, 'yaxis': 'y1'} self.assertEqual(dp['data'][0], expected_dp_data_hist) @@ -85,8 +82,8 @@ def test_simple_distplot(self): 'type': 'scatter', 'x': [1, 2, 2, 3], 'xaxis': 'x1', - 'y': ['distplot', 'distplot', 'distplot', - 'distplot'], + 'y': ['distplot', 'distplot', + 'distplot', 'distplot'], 'yaxis': 'y2'} self.assertEqual(dp['data'][2], expected_dp_data_rug) @@ -111,6 +108,7 @@ def test_distplot_more_args(self): expected_dp_layout = {'barmode': 'overlay', 'hovermode': 'closest', + 'legend': {'traceorder': 'reversed'}, 'title': 'Dist Plot', 'xaxis1': {'anchor': 'y2', 'domain': [0.0, 1.0], 'zeroline': False}, diff --git a/plotly/tools.py b/plotly/tools.py index 55671f95754..a93df9ccace 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1453,7 +1453,7 @@ class FigureFactory(object): """ @staticmethod - def validate_equal_length(*args): + def _validate_equal_length(*args): """ Validates that data lists or ndarrays are the same length. @@ -1465,7 +1465,7 @@ def validate_equal_length(*args): "should be the same length.") @staticmethod - def validate_ohlc(open, high, low, close, direction, **kwargs): + def _validate_ohlc(open, high, low, close, direction, **kwargs): """ ohlc and candlestick specific validations @@ -1508,7 +1508,7 @@ def validate_ohlc(open, high, low, close, direction, **kwargs): "'both'") @staticmethod - def validate_distplot(hist_data, curve_type): + def _validate_distplot(hist_data, curve_type): """ distplot specific validations @@ -1516,9 +1516,17 @@ def validate_distplot(hist_data, curve_type): :raises: (PlotlyError) If curve_type is not valid (i.e. not 'kde' or 'normal'). """ + try: + import pandas as pd + _pandas_imported = True + except ImportError: + _pandas_imported = False + hist_data_types = (list,) if _numpy_imported: hist_data_types += (np.ndarray,) + if _pandas_imported: + hist_data_types += (pd.core.series.Series,) if not isinstance(hist_data[0], hist_data_types): raise exceptions.PlotlyError("Oops, this function was written " @@ -1537,7 +1545,7 @@ def validate_distplot(hist_data, curve_type): raise ImportError("FigureFactory.create_distplot requires scipy") @staticmethod - def validate_positive_scalars(**kwargs): + def _validate_positive_scalars(**kwargs): """ Validates that all values given in key/val pairs are positive. @@ -1554,7 +1562,7 @@ def validate_positive_scalars(**kwargs): .format(key, val)) @staticmethod - def validate_streamline(x, y): + def _validate_streamline(x, y): """ streamline specific validations @@ -1580,7 +1588,7 @@ def validate_streamline(x, y): "evenly spaced array") @staticmethod - def flatten(array): + def _flatten(array): """ Uses list comprehension to flatten array @@ -1678,9 +1686,9 @@ def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3, py.plot(fig, filename='quiver') ``` """ - FigureFactory.validate_equal_length(x, y, u, v) - FigureFactory.validate_positive_scalars(arrow_scale=arrow_scale, - scale=scale) + FigureFactory._validate_equal_length(x, y, u, v) + FigureFactory._validate_positive_scalars(arrow_scale=arrow_scale, + scale=scale) barb_x, barb_y = _Quiver(x, y, u, v, scale, arrow_scale, angle).get_barbs() @@ -1779,10 +1787,10 @@ def create_streamline(x, y, u, v, py.plot(fig, filename='streamline') ``` """ - FigureFactory.validate_equal_length(x, y) - FigureFactory.validate_equal_length(u, v) - FigureFactory.validate_streamline(x, y) - FigureFactory.validate_positive_scalars(density=density, + FigureFactory._validate_equal_length(x, y) + FigureFactory._validate_equal_length(u, v) + FigureFactory._validate_streamline(x, y) + FigureFactory._validate_positive_scalars(density=density, arrow_scale=arrow_scale) streamline_x, streamline_y = _Streamline(x, y, u, v, @@ -2006,10 +2014,10 @@ def create_ohlc(open, high, low, close, ``` """ if dates is not None: - FigureFactory.validate_equal_length(open, high, low, close, dates) + FigureFactory._validate_equal_length(open, high, low, close, dates) else: - FigureFactory.validate_equal_length(open, high, low, close) - FigureFactory.validate_ohlc(open, high, low, close, direction, + FigureFactory._validate_equal_length(open, high, low, close) + FigureFactory._validate_ohlc(open, high, low, close, direction, **kwargs) if direction is 'increasing': @@ -2277,10 +2285,10 @@ def create_candlestick(open, high, low, close, ``` """ if dates is not None: - FigureFactory.validate_equal_length(open, high, low, close, dates) + FigureFactory._validate_equal_length(open, high, low, close, dates) else: - FigureFactory.validate_equal_length(open, high, low, close) - FigureFactory.validate_ohlc(open, high, low, close, direction, + FigureFactory._validate_equal_length(open, high, low, close) + FigureFactory._validate_ohlc(open, high, low, close, direction, **kwargs) if direction is 'increasing': @@ -2313,34 +2321,28 @@ def create_candlestick(open, high, low, close, @staticmethod def create_distplot(hist_data, group_labels, bin_size=1., curve_type='kde', + colors=[], rug_text=[], show_hist=True, show_curve=True, - show_rug=True, **kwargs): + show_rug=True): """ BETA function that creates a distplot similar to seaborn.distplot The distplot can be composed of all or any combination of the following 3 components: (1) histogram, (2) curve: (a) kernal density estimation - or (b)normal curve, and (3)rug plot. Additionally, multiple distplots + or (b) normal curve, and (3) rug plot. Additionally, multiple distplots (from multiple datasets) can be created in the same plot. - :param (list) hist_data: list of histogram data, should be a list of - lists if multiple data sets are plotted on the same plot - :param (list) group_labels: list of strings of the name of each data - set - :param (float) bin_size: size of histogram bin, default = 1. - :param (string) curve_type: 'kde' or 'normal', default = 'kde' - :param (bool) show_hist: True/False defines if histogram is added to - distplot, default = True - :param (bool) show_curve: True/False defines if curve is added to - distplot, default = True - :param (bool) show_rug: True/False defines if rug plot is added to - distplot, default = True - :param (list) colors: list of strings of color values, - default = first 10 Plot.ly colors - :param (list) rug_text: list of strings of hovertext for rug_plot, - default=None - - :rtype (dict): returns a representation of a distplot figure. + :param (list[list]) hist_data: Use list of lists to plot multiple data + sets on the same plot. + :param (list[str]) group_labels: Names for each data set. + :param (float) bin_size: Size of histogram bins. Default = 1. + :param (str) curve_type: 'kde' or 'normal'. Default = 'kde' + :param (bool) show_hist: Add histogram to distplot? Default = True + :param (bool) show_curve: Add curve to distplot? Default = True + :param (bool) show_rug: Add rug to distplot? Default = True + :param (list[str]) colors: Colors for traces. + :param (list[list]) rug_text: Hovertext values for rug_plot, + :return (dict): Representation of a distplot figure. Example 1: Simple distplot of 1 data set ``` @@ -2377,13 +2379,21 @@ def create_distplot(hist_data, group_labels, group_labels = ['2012', '2013'] - rug_text = ['a', 'b', 'c', 'd', 'e', - 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o'] + # Add text + rug_text_1 = ['a1', 'b1', 'c1', 'd1', 'e1', + 'f1', 'g1', 'h1', 'i1', 'j1', + 'k1', 'l1', 'm1', 'n1', 'o1'] + + rug_text_2 = ['a2', 'b2', 'c2', 'd2', 'e2', + 'f2', 'g2', 'h2', 'i2', 'j2', + 'k2', 'l2', 'm2', 'n2', 'o2'] + + # Group text together + rug_text_all = [rug_text_1] + [rug_text_2] # Create distplot fig = FF.create_distplot( - hist_data, group_labels, rug_text=rug_text, bin_size=.2) + hist_data, group_labels, rug_text=rug_text_all, bin_size=.2) # Add title fig['layout'].update(title='Dist Plot') @@ -2396,11 +2406,12 @@ def create_distplot(hist_data, group_labels, ``` import plotly.plotly as py from plotly.tools import FigureFactory as FF + import numpy as np - x1 = np.random.randn(20) - x2 = np.random.randn(40)+1 - x3 = np.random.randn(10)-1 - x4 = np.random.randn(10)+2 + x1 = np.random.randn(190) + x2 = np.random.randn(200)+1 + x3 = np.random.randn(200)-1 + x4 = np.random.randn(210)+2 hist_data = [x1] + [x2] + [x3] + [x4] group_labels = ['2012', '2013', '2014', '2015'] @@ -2410,24 +2421,44 @@ def create_distplot(hist_data, group_labels, show_rug=False, bin_size=.4) url = py.plot(fig, filename='hist and normal curve', validate=False) + + Example 4: Distplot with Pandas + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + import numpy as np + import pandas as pd + + df = pd.DataFrame({'2012': np.random.randn(200), + '2013': np.random.randn(200)+1}) + py.iplot(FF.create_distplot([df[c] for c in df.columns], df.columns), + filename='examples/distplot with pandas', + validate=False) + ``` """ - FigureFactory.validate_distplot(hist_data, curve_type) - FigureFactory.validate_equal_length(hist_data, group_labels) + FigureFactory._validate_distplot(hist_data, curve_type) + FigureFactory._validate_equal_length(hist_data, group_labels) hist = _Distplot( hist_data, group_labels, bin_size, - curve_type, **kwargs).make_hist() + curve_type, colors, rug_text, + show_hist, show_curve).make_hist() + if curve_type == 'normal': curve = _Distplot( hist_data, group_labels, bin_size, - curve_type, **kwargs).make_normal() + curve_type, colors, rug_text, + show_hist, show_curve).make_normal() else: curve = _Distplot( hist_data, group_labels, bin_size, - curve_type, **kwargs).make_kde() + curve_type, colors, rug_text, + show_hist, show_curve).make_kde() + rug = _Distplot( hist_data, group_labels, bin_size, - curve_type, **kwargs).make_rug() + curve_type, colors, rug_text, + show_hist, show_curve).make_rug() data = [] if show_hist: @@ -2439,6 +2470,7 @@ def create_distplot(hist_data, group_labels, layout = graph_objs.Layout( barmode='overlay', hovermode='closest', + legend=dict(traceorder='reversed'), xaxis1=dict(domain=[0.0, 1.0], anchor='y2', zeroline=False), @@ -2447,11 +2479,13 @@ def create_distplot(hist_data, group_labels, position=0.0), yaxis2=dict(domain=[0, 0.25], anchor='x1', - dtick=1)) + dtick=1, + showticklabels=False)) else: layout = graph_objs.Layout( barmode='overlay', hovermode='closest', + legend=dict(traceorder='reversed'), xaxis1=dict(domain=[0.0, 1.0], anchor='y2', zeroline=False), @@ -2472,22 +2506,22 @@ class _Quiver(FigureFactory): def __init__(self, x, y, u, v, scale, arrow_scale, angle, **kwargs): try: - x = FigureFactory.flatten(x) + x = FigureFactory._flatten(x) except exceptions.PlotlyError: pass try: - y = FigureFactory.flatten(y) + y = FigureFactory._flatten(y) except exceptions.PlotlyError: pass try: - u = FigureFactory.flatten(u) + u = FigureFactory._flatten(u) except exceptions.PlotlyError: pass try: - v = FigureFactory.flatten(v) + v = FigureFactory._flatten(v) except exceptions.PlotlyError: pass @@ -2531,8 +2565,8 @@ def get_barbs(self): self.end_x = [i + j for i, j in zip(self.x, self.u)] self.end_y = [i + j for i, j in zip(self.y, self.v)] empty = [None] * len(self.x) - barb_x = self.flatten(zip(self.x, self.end_x, empty)) - barb_y = self.flatten(zip(self.y, self.end_y, empty)) + barb_x = FigureFactory._flatten(zip(self.x, self.end_x, empty)) + barb_y = FigureFactory._flatten(zip(self.y, self.end_y, empty)) return barb_x, barb_y def get_quiver_arrows(self): @@ -2602,8 +2636,10 @@ def get_quiver_arrows(self): # Combine lists to create arrow empty = [None] * len(self.end_x) - arrow_x = self.flatten(zip(point1_x, self.end_x, point2_x, empty)) - arrow_y = self.flatten(zip(point1_y, self.end_y, point2_y, empty)) + arrow_x = FigureFactory._flatten(zip(point1_x, self.end_x, + point2_x, empty)) + arrow_y = FigureFactory._flatten(zip(point1_y, self.end_y, + point2_y, empty)) return arrow_x, arrow_y @@ -2951,8 +2987,8 @@ def get_increase(self): trace, flat_increase_y: y=values for the increasing trace and text_increase: hovertext for the increasing trace """ - flat_increase_x = FigureFactory.flatten(self.increase_x) - flat_increase_y = FigureFactory.flatten(self.increase_y) + flat_increase_x = FigureFactory._flatten(self.increase_x) + flat_increase_y = FigureFactory._flatten(self.increase_y) text_increase = (("Open", "Open", "High", "Low", "Close", "Close", '') * (len(self.increase_x))) @@ -2967,8 +3003,8 @@ def get_decrease(self): trace, flat_decrease_y: y=values for the decreasing trace and text_decrease: hovertext for the decreasing trace """ - flat_decrease_x = FigureFactory.flatten(self.decrease_x) - flat_decrease_y = FigureFactory.flatten(self.decrease_y) + flat_decrease_x = FigureFactory._flatten(self.decrease_x) + flat_decrease_y = FigureFactory._flatten(self.decrease_y) text_decrease = (("Open", "Open", "High", "Low", "Close", "Close", '') * (len(self.decrease_x))) @@ -3019,8 +3055,8 @@ def get_candle_increase(self): increase_close, increase_high, increase_empty)) stick_increase_x = [[x, x, x, x, None] for x in increase_x] - stick_increase_y = FigureFactory.flatten(stick_increase_y) - stick_increase_x = FigureFactory.flatten(stick_increase_x) + stick_increase_y = FigureFactory._flatten(stick_increase_y) + stick_increase_x = FigureFactory._flatten(stick_increase_x) return (increase_x, increase_open, increase_dif, stick_increase_y, stick_increase_x) @@ -3055,8 +3091,8 @@ def get_candle_decrease(self): decrease_empty)) stick_decrease_x = [[x, x, x, x, None] for x in decrease_x] - stick_decrease_y = FigureFactory.flatten(stick_decrease_y) - stick_decrease_x = FigureFactory.flatten(stick_decrease_x) + stick_decrease_y = FigureFactory._flatten(stick_decrease_y) + stick_decrease_x = FigureFactory._flatten(stick_decrease_x) return (decrease_x, decrease_close, decrease_dif, stick_decrease_y, stick_decrease_x) @@ -3067,21 +3103,23 @@ class _Distplot(FigureFactory): Refer to TraceFactory.create_distplot() for docstring """ def __init__(self, hist_data, group_labels, - bin_size, curve_type, - **kwargs): + bin_size, curve_type, colors, + rug_text, show_hist, show_curve): self.hist_data = hist_data self.group_labels = group_labels self.bin_size = bin_size - if 'rug_text' in kwargs: + self.show_hist = show_hist + self.show_curve = show_curve + self.trace_number = len(hist_data) + if rug_text: self.rug_text = rug_text else: - self.rug_text = None - self.trace_number = len(hist_data) + self.rug_text = [None] * self.trace_number self.start = [] self.end = [] - if 'colors' in kwargs: - self.colors = kwargs['colors'] + if colors: + self.colors = 'colors' else: self.colors = [ "rgb(31, 119, 180)", "rgb(255, 127, 14)", @@ -3147,7 +3185,7 @@ def make_kde(self): mode='lines', name=self.group_labels[index], legendgroup=self.group_labels[index], - showlegend=False, + showlegend=False if self.show_hist else True, marker=dict(color=self.colors[index])) return curve @@ -3182,7 +3220,7 @@ def make_normal(self): mode='lines', name=self.group_labels[index], legendgroup=self.group_labels[index], - showlegend=False, + showlegend=False if self.show_hist else True, marker=dict(color=self.colors[index])) return curve @@ -3204,8 +3242,9 @@ def make_rug(self): mode='markers', name=self.group_labels[index], legendgroup=self.group_labels[index], - showlegend=False, - text=self.rug_text, + showlegend=(False if self.show_hist or + self.show_curve else True), + text=self.rug_text[index], marker=dict(color=self.colors[index], symbol='line-ns-open')) return rug From 2e14a189e4202c42d897e3e5c5233b6ac7af7baf Mon Sep 17 00:00:00 2001 From: Chelsea Date: Tue, 8 Sep 2015 15:40:08 -0400 Subject: [PATCH 15/18] edit list combination in examples --- plotly/tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 20e56d06295..59fbbb333c2 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -2339,7 +2339,7 @@ def create_distplot(hist_data, group_labels, -0.3, 1.2, 0.56, 0.3, 2.2] # Group data together - hist_data = [hist1_x] + [hist2_x] + hist_data = [hist1_x, hist2_x] group_labels = ['2012', '2013'] @@ -2353,7 +2353,7 @@ def create_distplot(hist_data, group_labels, 'k2', 'l2', 'm2', 'n2', 'o2'] # Group text together - rug_text_all = [rug_text_1] + [rug_text_2] + rug_text_all = [rug_text_1, rug_text_2] # Create distplot fig = FF.create_distplot( @@ -2377,7 +2377,7 @@ def create_distplot(hist_data, group_labels, x3 = np.random.randn(200)-1 x4 = np.random.randn(210)+2 - hist_data = [x1] + [x2] + [x3] + [x4] + hist_data = [x1, x2, x3, x4] group_labels = ['2012', '2013', '2014', '2015'] fig = FF.create_distplot( From f91130ea87a5bd9c673d1bd869e0c31915818c1f Mon Sep 17 00:00:00 2001 From: Chelsea Date: Fri, 11 Sep 2015 16:55:10 -0400 Subject: [PATCH 16/18] missed a `_` to hide `FF.flatten()` --- plotly/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 59fbbb333c2..64632f9bac5 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3011,7 +3011,7 @@ def get_candle_increase(self): increase_x.append(self.x[index]) increase_x = [[x, x, x, x, x, x] for x in increase_x] - increase_x = FigureFactory.flatten(increase_x) + increase_x = FigureFactory._flatten(increase_x) return increase_x, increase_y @@ -3035,7 +3035,7 @@ def get_candle_decrease(self): decrease_x.append(self.x[index]) decrease_x = [[x, x, x, x, x, x] for x in decrease_x] - decrease_x = FigureFactory.flatten(decrease_x) + decrease_x = FigureFactory._flatten(decrease_x) return decrease_x, decrease_y From f46b00f63a585d09e4455f02c84cae52ccf8f9fd Mon Sep 17 00:00:00 2001 From: Chelsea Date: Fri, 11 Sep 2015 17:39:29 -0400 Subject: [PATCH 17/18] comment out test to check import error - scipy will currently import in test_core --- .../test_tools/test_figure_factory.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index fdc8c4ff7c5..ff90b0d2cea 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -687,21 +687,21 @@ def test_datetime_candlestick(self): self.assertEqual(candle, exp_candle) -class TestDistplot(TestCase): +# class TestDistplot(TestCase): - def test_scipy_import_error(self): +# def test_scipy_import_error(self): - # make sure Import Error is raised when _scipy_imported = False +# # make sure Import Error is raised when _scipy_imported = False - hist_data = [[1.1, 1.1, 2.5, 3.0, 3.5, - 3.5, 4.1, 4.4, 4.5, 4.5, - 5.0, 5.0, 5.2, 5.5, 5.5, - 5.5, 5.5, 5.5, 6.1, 7.0]] +# hist_data = [[1.1, 1.1, 2.5, 3.0, 3.5, +# 3.5, 4.1, 4.4, 4.5, 4.5, +# 5.0, 5.0, 5.2, 5.5, 5.5, +# 5.5, 5.5, 5.5, 6.1, 7.0]] - group_labels = ['distplot example'] +# group_labels = ['distplot example'] - self.assertRaisesRegexp(ImportError, - "FigureFactory.create_distplot requires scipy", - tls.FigureFactory.create_distplot, - hist_data, group_labels) +# self.assertRaisesRegexp(ImportError, +# "FigureFactory.create_distplot requires scipy", +# tls.FigureFactory.create_distplot, +# hist_data, group_labels) From 4702523110cd8fbe5bab43b3aada5d5e98a6f56b Mon Sep 17 00:00:00 2001 From: Chelsea Date: Mon, 14 Sep 2015 11:29:09 -0400 Subject: [PATCH 18/18] fix colors variable --- plotly/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/tools.py b/plotly/tools.py index 64632f9bac5..bb175bba7ec 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3061,7 +3061,7 @@ def __init__(self, hist_data, group_labels, self.start = [] self.end = [] if colors: - self.colors = 'colors' + self.colors = colors else: self.colors = [ "rgb(31, 119, 180)", "rgb(255, 127, 14)",