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 c79bde4eb2a..2c5c0eda9f7 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1125,7 +1125,6 @@ def test_table_with_index(self): 'zeroline': False}}} self.assertEqual(index_table, exp_index_table) - # class TestDistplot(TestCase): # def test_scipy_import_error(self): diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 608320338e6..ad64fa82fb3 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -784,15 +784,22 @@ def test_valid_colormap(self): tri = Delaunay(points2D) simplices = tri.simplices - # check that a valid plotly colorscale name is entered - self.assertRaises(PlotlyError, tls.FigureFactory.create_trisurf, - x, y, z, simplices, colormap='foo') + # check that a valid plotly colorscale string is entered + + pattern = ( + "If your colors variable is a string, it must be a Plotly scale, " + "an rgb color or a hex color." + ) + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_trisurf, + x, y, z, simplices, colormap='foo') # check: if colormap is a list of rgb color strings, make sure the # entries of each color are no greater than 255.0 pattern2 = ( - "Whoops! The elements in your rgb colormap tuples " + "Whoops! The elements in your rgb colors tuples " "cannot exceed 255.0." ) @@ -805,7 +812,7 @@ def test_valid_colormap(self): # of each tuple are no greater than 1.0 pattern3 = ( - "Whoops! The elements in your colormap tuples cannot exceed 1.0." + "Whoops! The elements in your colors tuples cannot exceed 1.0." ) self.assertRaisesRegexp(PlotlyError, pattern3, @@ -813,18 +820,6 @@ def test_valid_colormap(self): x, y, z, simplices, colormap=[(0.8, 1.0, 1.2)]) - # check: - - pattern4 = ( - "You must input a valid colormap. Valid types include a plotly " - "scale, rgb, hex or tuple color, or lastly a list of any color " - "types." - ) - - self.assertRaisesRegexp(PlotlyError, pattern4, - tls.FigureFactory.create_trisurf, - x, y, z, simplices, colormap=1) - def test_trisurf_all_args(self): # check if trisurf plot matches with expected output @@ -849,14 +844,14 @@ def test_trisurf_all_args(self): exp_trisurf_plot = { 'data': [ { - 'facecolor': ['rgb(143.0, 123.0, 97.0)', - 'rgb(255.0, 127.0, 14.0)', - 'rgb(143.0, 123.0, 97.0)', + 'facecolor': ['rgb(144.0, 94.5, 132.0)', + 'rgb(23.0, 190.0, 207.0)', + 'rgb(144.0, 94.5, 132.0)', 'rgb(31.0, 119.0, 180.0)', - 'rgb(143.0, 123.0, 97.0)', + 'rgb(144.0, 94.5, 132.0)', 'rgb(31.0, 119.0, 180.0)', - 'rgb(143.0, 123.0, 97.0)', - 'rgb(255.0, 127.0, 14.0)'], + 'rgb(144.0, 94.5, 132.0)', + 'rgb(23.0, 190.0, 207.0)'], 'i': [3, 1, 1, 5, 7, 3, 5, 7], 'j': [1, 3, 5, 1, 3, 7, 7, 5], 'k': [4, 0, 4, 2, 4, 6, 4, 8], @@ -1057,19 +1052,8 @@ def test_valid_colormap(self): tls.FigureFactory.create_scatterplotmatrix, df, index='a', colormap='fake_scale') - pattern = ( - "You must input a valid colormap. Valid types include a plotly " - "scale, rgb, hex or tuple color, a list of any color types, or a " - "dictionary with index names each assigned to a color." - ) - - # check: accepted data type for colormap - self.assertRaisesRegexp(PlotlyError, pattern, - tls.FigureFactory.create_scatterplotmatrix, - df, colormap=1) - pattern_rgb = ( - "Whoops! The elements in your rgb colormap tuples cannot " + "Whoops! The elements in your rgb colors tuples cannot " "exceed 255.0." ) @@ -1083,7 +1067,7 @@ def test_valid_colormap(self): df, colormap=['rgb(500, 1, 1)'], index='c') pattern_tuple = ( - "Whoops! The elements in your colormap tuples cannot " + "Whoops! The elements in your colors tuples cannot " "exceed 1.0." ) @@ -1248,13 +1232,13 @@ def test_scatter_plot_matrix_kwargs(self): ) exp_scatter_plot_matrix = { - 'data': [{'marker': {'color': 'rgb(128,0,38)'}, + 'data': [{'marker': {'color': 'rgb(128.0, 0.0, 38.0)'}, 'showlegend': False, 'type': 'histogram', 'x': [2, -15, -2, 0], 'xaxis': 'x1', 'yaxis': 'y1'}, - {'marker': {'color': 'rgb(255,255,204)'}, + {'marker': {'color': 'rgb(255.0, 255.0, 204.0)'}, 'showlegend': False, 'type': 'histogram', 'x': [6, 5], @@ -1281,3 +1265,593 @@ def test_scatter_plot_matrix_kwargs(self): self.assert_dict_equal(test_scatter_plot_matrix['layout'], exp_scatter_plot_matrix['layout']) + + +class TestViolin(NumpyTestUtilsMixin, TestCase): + + def test_colors_validation(self): + + # check: colors is in an acceptable form + + data = [1, 5, 8] + + pattern = ("Whoops! The elements in your rgb colors tuples cannot " + "exceed 255.0.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, + data, colors='rgb(300, 2, 3)') + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, + data, colors=['rgb(300, 2, 3)']) + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, + data, colors={'apple': 'rgb(300, 2, 3)'}) + + pattern2 = ("Whoops! The elements in your colors tuples cannot " + "exceed 1.0.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_violin, + data, colors=(1.1, 1, 1)) + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_violin, + data, colors=[(1.1, 1, 1)]) + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_violin, + data, colors={'apple': (1.1, 1, 1)}) + + # check: if valid string color is inputted + self.assertRaises(PlotlyError, tls.FigureFactory.create_violin, + data, colors='foo') + + def test_data_header(self): + + # make sure data_header is entered + + data = pd.DataFrame([['apple', 2], ['pear', 4]], + columns=['a', 'b']) + + pattern = ("data_header must be the column name with the desired " + "numeric data for the violin plot.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, data, + group_header='a', colors=['rgb(1, 2, 3)']) + + def test_data_as_list(self): + + # check: data is a non empty list of numbers + + data = [] + + pattern = ("If data is a list, it must be nonempty and contain " + "either numbers or dictionaries.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, + data) + + data = [1, 'foo'] + + pattern2 = ("If data is a list, it must contain only numbers.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_violin, data) + + def test_dataframe_input(self): + + # check: dataframe is entered if group_header is True + + data = [1, 2, 3] + + pattern = ("Error. You must use a pandas DataFrame if you are using " + "a group header.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, data, + group_header=True) + + def test_colors_dict(self): + + # check: if colorscale is True, make sure colors is not a dictionary + + data = pd.DataFrame([['apple', 2], ['pear', 4]], + columns=['a', 'b']) + + pattern = ("The colors param cannot be a dictionary if you are " + "using a colorscale.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, data, + data_header='b', group_header='a', + use_colorscale=True, + colors={'a': 'rgb(1, 2, 3)'}) + + # check: colors contains all group names as keys + + pattern2 = ("If colors is a dictionary, all the group names must " + "appear as keys in colors.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_violin, data, + data_header='b', group_header='a', + use_colorscale=False, + colors={'a': 'rgb(1, 2, 3)'}) + + def test_valid_colorscale(self): + + # check: if colorscale is enabled, colors is a list with 2+ items + + data = pd.DataFrame([['apple', 2], ['pear', 4]], + columns=['a', 'b']) + + pattern = ("colors must be a list with at least 2 colors. A Plotly " + "scale is allowed.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, data, + data_header='b', group_header='a', + use_colorscale=True, + colors='rgb(1, 2, 3)') + + def test_group_stats(self): + + # check: group_stats is a dictionary + + data = pd.DataFrame([['apple', 2], ['pear', 4]], + columns=['a', 'b']) + + pattern = ("Your group_stats param must be a dictionary.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_violin, data, + data_header='b', group_header='a', + use_colorscale=True, + colors=['rgb(1, 2, 3)', 'rgb(4, 5, 6)'], + group_stats=1) + + # check: all groups are represented as keys in group_stats + + pattern2 = ("All values/groups in the index column must be " + "represented as a key in group_stats.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_violin, data, + data_header='b', group_header='a', + use_colorscale=True, + colors=['rgb(1, 2, 3)', 'rgb(4, 5, 6)'], + group_stats={'apple': 1}) + + def test_violin_fig(self): + + # check: test violin fig matches expected fig + + test_violin = tls.FigureFactory.create_violin(data=[1, 2]) + + exp_violin = { + 'data': [{'fill': 'tonextx', + 'fillcolor': 'rgb(31.0, 119.0, 180.0)', + 'hoverinfo': 'text', + 'line': {'color': 'rgb(0, 0, 0)', + 'shape': 'spline', + 'width': 0.5}, + 'mode': 'lines', + 'name': '', + 'opacity': 0.5, + 'text': ['(pdf(y), y)=(-0.41, 1.00)', + '(pdf(y), y)=(-0.41, 1.01)', + '(pdf(y), y)=(-0.42, 1.02)', + '(pdf(y), y)=(-0.42, 1.03)', + '(pdf(y), y)=(-0.42, 1.04)', + '(pdf(y), y)=(-0.42, 1.05)', + '(pdf(y), y)=(-0.42, 1.06)', + '(pdf(y), y)=(-0.43, 1.07)', + '(pdf(y), y)=(-0.43, 1.08)', + '(pdf(y), y)=(-0.43, 1.09)', + '(pdf(y), y)=(-0.43, 1.10)', + '(pdf(y), y)=(-0.43, 1.11)', + '(pdf(y), y)=(-0.43, 1.12)', + '(pdf(y), y)=(-0.44, 1.13)', + '(pdf(y), y)=(-0.44, 1.14)', + '(pdf(y), y)=(-0.44, 1.15)', + '(pdf(y), y)=(-0.44, 1.16)', + '(pdf(y), y)=(-0.44, 1.17)', + '(pdf(y), y)=(-0.44, 1.18)', + '(pdf(y), y)=(-0.45, 1.19)', + '(pdf(y), y)=(-0.45, 1.20)', + '(pdf(y), y)=(-0.45, 1.21)', + '(pdf(y), y)=(-0.45, 1.22)', + '(pdf(y), y)=(-0.45, 1.23)', + '(pdf(y), y)=(-0.45, 1.24)', + '(pdf(y), y)=(-0.45, 1.25)', + '(pdf(y), y)=(-0.45, 1.26)', + '(pdf(y), y)=(-0.45, 1.27)', + '(pdf(y), y)=(-0.46, 1.28)', + '(pdf(y), y)=(-0.46, 1.29)', + '(pdf(y), y)=(-0.46, 1.30)', + '(pdf(y), y)=(-0.46, 1.31)', + '(pdf(y), y)=(-0.46, 1.32)', + '(pdf(y), y)=(-0.46, 1.33)', + '(pdf(y), y)=(-0.46, 1.34)', + '(pdf(y), y)=(-0.46, 1.35)', + '(pdf(y), y)=(-0.46, 1.36)', + '(pdf(y), y)=(-0.46, 1.37)', + '(pdf(y), y)=(-0.46, 1.38)', + '(pdf(y), y)=(-0.46, 1.39)', + '(pdf(y), y)=(-0.46, 1.40)', + '(pdf(y), y)=(-0.46, 1.41)', + '(pdf(y), y)=(-0.46, 1.42)', + '(pdf(y), y)=(-0.47, 1.43)', + '(pdf(y), y)=(-0.47, 1.44)', + '(pdf(y), y)=(-0.47, 1.45)', + '(pdf(y), y)=(-0.47, 1.46)', + '(pdf(y), y)=(-0.47, 1.47)', + '(pdf(y), y)=(-0.47, 1.48)', + '(pdf(y), y)=(-0.47, 1.49)', + '(pdf(y), y)=(-0.47, 1.51)', + '(pdf(y), y)=(-0.47, 1.52)', + '(pdf(y), y)=(-0.47, 1.53)', + '(pdf(y), y)=(-0.47, 1.54)', + '(pdf(y), y)=(-0.47, 1.55)', + '(pdf(y), y)=(-0.47, 1.56)', + '(pdf(y), y)=(-0.47, 1.57)', + '(pdf(y), y)=(-0.46, 1.58)', + '(pdf(y), y)=(-0.46, 1.59)', + '(pdf(y), y)=(-0.46, 1.60)', + '(pdf(y), y)=(-0.46, 1.61)', + '(pdf(y), y)=(-0.46, 1.62)', + '(pdf(y), y)=(-0.46, 1.63)', + '(pdf(y), y)=(-0.46, 1.64)', + '(pdf(y), y)=(-0.46, 1.65)', + '(pdf(y), y)=(-0.46, 1.66)', + '(pdf(y), y)=(-0.46, 1.67)', + '(pdf(y), y)=(-0.46, 1.68)', + '(pdf(y), y)=(-0.46, 1.69)', + '(pdf(y), y)=(-0.46, 1.70)', + '(pdf(y), y)=(-0.46, 1.71)', + '(pdf(y), y)=(-0.46, 1.72)', + '(pdf(y), y)=(-0.45, 1.73)', + '(pdf(y), y)=(-0.45, 1.74)', + '(pdf(y), y)=(-0.45, 1.75)', + '(pdf(y), y)=(-0.45, 1.76)', + '(pdf(y), y)=(-0.45, 1.77)', + '(pdf(y), y)=(-0.45, 1.78)', + '(pdf(y), y)=(-0.45, 1.79)', + '(pdf(y), y)=(-0.45, 1.80)', + '(pdf(y), y)=(-0.45, 1.81)', + '(pdf(y), y)=(-0.44, 1.82)', + '(pdf(y), y)=(-0.44, 1.83)', + '(pdf(y), y)=(-0.44, 1.84)', + '(pdf(y), y)=(-0.44, 1.85)', + '(pdf(y), y)=(-0.44, 1.86)', + '(pdf(y), y)=(-0.44, 1.87)', + '(pdf(y), y)=(-0.43, 1.88)', + '(pdf(y), y)=(-0.43, 1.89)', + '(pdf(y), y)=(-0.43, 1.90)', + '(pdf(y), y)=(-0.43, 1.91)', + '(pdf(y), y)=(-0.43, 1.92)', + '(pdf(y), y)=(-0.43, 1.93)', + '(pdf(y), y)=(-0.42, 1.94)', + '(pdf(y), y)=(-0.42, 1.95)', + '(pdf(y), y)=(-0.42, 1.96)', + '(pdf(y), y)=(-0.42, 1.97)', + '(pdf(y), y)=(-0.42, 1.98)', + '(pdf(y), y)=(-0.41, 1.99)', + '(pdf(y), y)=(-0.41, 2.00)'], + 'type': 'scatter', + 'x': np.array([-0.41064744, -0.41293151, -0.41516635, + -0.41735177, -0.41948764, -0.42157385, + -0.42361031, -0.42559697, -0.42753381, + -0.42942082, -0.43125804, -0.43304552, + -0.43478334, -0.4364716, -0.4381104, + -0.4396999, -0.44124025, -0.44273162, + -0.4441742, -0.4455682, -0.44691382, + -0.44821129, -0.44946086, -0.45066275, + -0.45181723, -0.45292454, -0.45398495, + -0.45499871, -0.45596609, -0.45688735, + -0.45776275, -0.45859254, -0.45937698, + -0.46011631, -0.46081078, -0.46146061, + -0.46206603, -0.46262726, -0.46314449, + -0.46361791, -0.4640477, -0.46443404, + -0.46477705, -0.46507689, -0.46533367, + -0.46554749, -0.46571845, -0.4658466, + -0.46593201, -0.4659747, -0.4659747, + -0.46593201, -0.4658466, -0.46571845, + -0.46554749, -0.46533367, -0.46507689, + -0.46477705, -0.46443404, -0.4640477, + -0.46361791, -0.46314449, -0.46262726, + -0.46206603, -0.46146061, -0.46081078, + -0.46011631, -0.45937698, -0.45859254, + -0.45776275, -0.45688735, -0.45596609, + -0.45499871, -0.45398495, -0.45292454, + -0.45181723, -0.45066275, -0.44946086, + -0.44821129, -0.44691382, -0.4455682, + -0.4441742, -0.44273162, -0.44124025, + -0.4396999, -0.4381104, -0.4364716, + -0.43478334, -0.43304552, -0.43125804, + -0.42942082, -0.42753381, -0.42559697, + -0.42361031, -0.42157385, -0.41948764, + -0.41735177, -0.41516635, -0.41293151, + -0.41064744]), + 'y': np.array([1., 1.01010101, 1.02020202, + 1.03030303, 1.04040404, 1.05050505, + 1.06060606, 1.07070707, 1.08080808, + 1.09090909, 1.1010101, 1.11111111, + 1.12121212, 1.13131313, 1.14141414, + 1.15151515, 1.16161616, 1.17171717, + 1.18181818, 1.19191919, 1.2020202, + 1.21212121, 1.22222222, 1.23232323, + 1.24242424, 1.25252525, 1.26262626, + 1.27272727, 1.28282828, 1.29292929, + 1.3030303, 1.31313131, 1.32323232, + 1.33333333, 1.34343434, 1.35353535, + 1.36363636, 1.37373737, 1.38383838, + 1.39393939, 1.4040404, 1.41414141, + 1.42424242, 1.43434343, 1.44444444, + 1.45454545, 1.46464646, 1.47474747, + 1.48484848, 1.49494949, 1.50505051, + 1.51515152, 1.52525253, 1.53535354, + 1.54545455, 1.55555556, 1.56565657, + 1.57575758, 1.58585859, 1.5959596, + 1.60606061, 1.61616162, 1.62626263, + 1.63636364, 1.64646465, 1.65656566, + 1.66666667, 1.67676768, 1.68686869, + 1.6969697, 1.70707071, 1.71717172, + 1.72727273, 1.73737374, 1.74747475, + 1.75757576, 1.76767677, 1.77777778, + 1.78787879, 1.7979798, 1.80808081, + 1.81818182, 1.82828283, 1.83838384, + 1.84848485, 1.85858586, 1.86868687, + 1.87878788, 1.88888889, 1.8989899, + 1.90909091, 1.91919192, 1.92929293, + 1.93939394, 1.94949495, 1.95959596, + 1.96969697, 1.97979798, 1.98989899, + 2.])}, + {'fill': 'tonextx', + 'fillcolor': 'rgb(31.0, 119.0, 180.0)', + 'hoverinfo': 'text', + 'line': {'color': 'rgb(0, 0, 0)', + 'shape': 'spline', + 'width': 0.5}, + 'mode': 'lines', + 'name': '', + 'opacity': 0.5, + 'text': ['(pdf(y), y)=(0.41, 1.00)', + '(pdf(y), y)=(0.41, 1.01)', + '(pdf(y), y)=(0.42, 1.02)', + '(pdf(y), y)=(0.42, 1.03)', + '(pdf(y), y)=(0.42, 1.04)', + '(pdf(y), y)=(0.42, 1.05)', + '(pdf(y), y)=(0.42, 1.06)', + '(pdf(y), y)=(0.43, 1.07)', + '(pdf(y), y)=(0.43, 1.08)', + '(pdf(y), y)=(0.43, 1.09)', + '(pdf(y), y)=(0.43, 1.10)', + '(pdf(y), y)=(0.43, 1.11)', + '(pdf(y), y)=(0.43, 1.12)', + '(pdf(y), y)=(0.44, 1.13)', + '(pdf(y), y)=(0.44, 1.14)', + '(pdf(y), y)=(0.44, 1.15)', + '(pdf(y), y)=(0.44, 1.16)', + '(pdf(y), y)=(0.44, 1.17)', + '(pdf(y), y)=(0.44, 1.18)', + '(pdf(y), y)=(0.45, 1.19)', + '(pdf(y), y)=(0.45, 1.20)', + '(pdf(y), y)=(0.45, 1.21)', + '(pdf(y), y)=(0.45, 1.22)', + '(pdf(y), y)=(0.45, 1.23)', + '(pdf(y), y)=(0.45, 1.24)', + '(pdf(y), y)=(0.45, 1.25)', + '(pdf(y), y)=(0.45, 1.26)', + '(pdf(y), y)=(0.45, 1.27)', + '(pdf(y), y)=(0.46, 1.28)', + '(pdf(y), y)=(0.46, 1.29)', + '(pdf(y), y)=(0.46, 1.30)', + '(pdf(y), y)=(0.46, 1.31)', + '(pdf(y), y)=(0.46, 1.32)', + '(pdf(y), y)=(0.46, 1.33)', + '(pdf(y), y)=(0.46, 1.34)', + '(pdf(y), y)=(0.46, 1.35)', + '(pdf(y), y)=(0.46, 1.36)', + '(pdf(y), y)=(0.46, 1.37)', + '(pdf(y), y)=(0.46, 1.38)', + '(pdf(y), y)=(0.46, 1.39)', + '(pdf(y), y)=(0.46, 1.40)', + '(pdf(y), y)=(0.46, 1.41)', + '(pdf(y), y)=(0.46, 1.42)', + '(pdf(y), y)=(0.47, 1.43)', + '(pdf(y), y)=(0.47, 1.44)', + '(pdf(y), y)=(0.47, 1.45)', + '(pdf(y), y)=(0.47, 1.46)', + '(pdf(y), y)=(0.47, 1.47)', + '(pdf(y), y)=(0.47, 1.48)', + '(pdf(y), y)=(0.47, 1.49)', + '(pdf(y), y)=(0.47, 1.51)', + '(pdf(y), y)=(0.47, 1.52)', + '(pdf(y), y)=(0.47, 1.53)', + '(pdf(y), y)=(0.47, 1.54)', + '(pdf(y), y)=(0.47, 1.55)', + '(pdf(y), y)=(0.47, 1.56)', + '(pdf(y), y)=(0.47, 1.57)', + '(pdf(y), y)=(0.46, 1.58)', + '(pdf(y), y)=(0.46, 1.59)', + '(pdf(y), y)=(0.46, 1.60)', + '(pdf(y), y)=(0.46, 1.61)', + '(pdf(y), y)=(0.46, 1.62)', + '(pdf(y), y)=(0.46, 1.63)', + '(pdf(y), y)=(0.46, 1.64)', + '(pdf(y), y)=(0.46, 1.65)', + '(pdf(y), y)=(0.46, 1.66)', + '(pdf(y), y)=(0.46, 1.67)', + '(pdf(y), y)=(0.46, 1.68)', + '(pdf(y), y)=(0.46, 1.69)', + '(pdf(y), y)=(0.46, 1.70)', + '(pdf(y), y)=(0.46, 1.71)', + '(pdf(y), y)=(0.46, 1.72)', + '(pdf(y), y)=(0.45, 1.73)', + '(pdf(y), y)=(0.45, 1.74)', + '(pdf(y), y)=(0.45, 1.75)', + '(pdf(y), y)=(0.45, 1.76)', + '(pdf(y), y)=(0.45, 1.77)', + '(pdf(y), y)=(0.45, 1.78)', + '(pdf(y), y)=(0.45, 1.79)', + '(pdf(y), y)=(0.45, 1.80)', + '(pdf(y), y)=(0.45, 1.81)', + '(pdf(y), y)=(0.44, 1.82)', + '(pdf(y), y)=(0.44, 1.83)', + '(pdf(y), y)=(0.44, 1.84)', + '(pdf(y), y)=(0.44, 1.85)', + '(pdf(y), y)=(0.44, 1.86)', + '(pdf(y), y)=(0.44, 1.87)', + '(pdf(y), y)=(0.43, 1.88)', + '(pdf(y), y)=(0.43, 1.89)', + '(pdf(y), y)=(0.43, 1.90)', + '(pdf(y), y)=(0.43, 1.91)', + '(pdf(y), y)=(0.43, 1.92)', + '(pdf(y), y)=(0.43, 1.93)', + '(pdf(y), y)=(0.42, 1.94)', + '(pdf(y), y)=(0.42, 1.95)', + '(pdf(y), y)=(0.42, 1.96)', + '(pdf(y), y)=(0.42, 1.97)', + '(pdf(y), y)=(0.42, 1.98)', + '(pdf(y), y)=(0.41, 1.99)', + '(pdf(y), y)=(0.41, 2.00)'], + 'type': 'scatter', + 'x': np.array([0.41064744, 0.41293151, 0.41516635, + 0.41735177, 0.41948764, 0.42157385, + 0.42361031, 0.42559697, 0.42753381, + 0.42942082, 0.43125804, 0.43304552, + 0.43478334, 0.4364716, 0.4381104, + 0.4396999, 0.44124025, 0.44273162, + 0.4441742, 0.4455682, 0.44691382, + 0.44821129, 0.44946086, 0.45066275, + 0.45181723, 0.45292454, 0.45398495, + 0.45499871, 0.45596609, 0.45688735, + 0.45776275, 0.45859254, 0.45937698, + 0.46011631, 0.46081078, 0.46146061, + 0.46206603, 0.46262726, 0.46314449, + 0.46361791, 0.4640477, 0.46443404, + 0.46477705, 0.46507689, 0.46533367, + 0.46554749, 0.46571845, 0.4658466, + 0.46593201, 0.4659747, 0.4659747, + 0.46593201, 0.4658466, 0.46571845, + 0.46554749, 0.46533367, 0.46507689, + 0.46477705, 0.46443404, 0.4640477, + 0.46361791, 0.46314449, 0.46262726, + 0.46206603, 0.46146061, 0.46081078, + 0.46011631, 0.45937698, 0.45859254, + 0.45776275, 0.45688735, 0.45596609, + 0.45499871, 0.45398495, 0.45292454, + 0.45181723, 0.45066275, 0.44946086, + 0.44821129, 0.44691382, 0.4455682, + 0.4441742, 0.44273162, 0.44124025, + 0.4396999, 0.4381104, 0.4364716, + 0.43478334, 0.43304552, 0.43125804, + 0.42942082, 0.42753381, 0.42559697, + 0.42361031, 0.42157385, 0.41948764, + 0.41735177, 0.41516635, 0.41293151, + 0.41064744]), + 'y': np.array([1., 1.01010101, 1.02020202, + 1.03030303, 1.04040404, 1.05050505, + 1.06060606, 1.07070707, 1.08080808, + 1.09090909, 1.1010101, 1.11111111, + 1.12121212, 1.13131313, 1.14141414, + 1.15151515, 1.16161616, 1.17171717, + 1.18181818, 1.19191919, 1.2020202, + 1.21212121, 1.22222222, 1.23232323, + 1.24242424, 1.25252525, 1.26262626, + 1.27272727, 1.28282828, 1.29292929, + 1.3030303, 1.31313131, 1.32323232, + 1.33333333, 1.34343434, 1.35353535, + 1.36363636, 1.37373737, 1.38383838, + 1.39393939, 1.4040404, 1.41414141, + 1.42424242, 1.43434343, 1.44444444, + 1.45454545, 1.46464646, 1.47474747, + 1.48484848, 1.49494949, 1.50505051, + 1.51515152, 1.52525253, 1.53535354, + 1.54545455, 1.55555556, 1.56565657, + 1.57575758, 1.58585859, 1.5959596, + 1.60606061, 1.61616162, 1.62626263, + 1.63636364, 1.64646465, 1.65656566, + 1.66666667, 1.67676768, 1.68686869, + 1.6969697, 1.70707071, 1.71717172, + 1.72727273, 1.73737374, 1.74747475, + 1.75757576, 1.76767677, 1.77777778, + 1.78787879, 1.7979798, 1.80808081, + 1.81818182, 1.82828283, 1.83838384, + 1.84848485, 1.85858586, 1.86868687, + 1.87878788, 1.88888889, 1.8989899, + 1.90909091, 1.91919192, 1.92929293, + 1.93939394, 1.94949495, 1.95959596, + 1.96969697, 1.97979798, 1.98989899, + 2.])}, + {'line': {'color': 'rgb(0,0,0)', 'width': 1.5}, + 'mode': 'lines', + 'name': '', + 'type': 'scatter', + 'x': [0, 0], + 'y': [1.0, 2.0]}, + {'hoverinfo': 'text', + 'line': {'color': 'rgb(0,0,0)', 'width': 4}, + 'mode': 'lines', + 'text': ['lower-quartile: 1.00', + 'upper-quartile: 2.00'], + 'type': 'scatter', + 'x': [0, 0], + 'y': [1.0, 2.0]}, + {'hoverinfo': 'text', + 'marker': {'color': 'rgb(255,255,255)', + 'symbol': 'square'}, + 'mode': 'markers', + 'text': ['median: 1.50'], + 'type': 'scatter', + 'x': [0], + 'y': [1.5]}, + {'hoverinfo': 'y', + 'marker': {'color': 'rgb(31.0, 119.0, 180.0)', + 'symbol': 'line-ew-open'}, + 'mode': 'markers', + 'name': '', + 'showlegend': False, + 'type': 'scatter', + 'x': [-0.55916964093970667, -0.55916964093970667], + 'y': np.array([1., 2.])}], + 'layout': {'autosize': False, + 'font': {'size': 11}, + 'height': 450, + 'hovermode': 'closest', + 'showlegend': False, + 'title': 'Violin and Rug Plot', + 'width': 600, + 'xaxis': {'mirror': False, + 'range': [-0.65916964093970665, + 0.56597470078308887], + 'showgrid': False, + 'showline': False, + 'showticklabels': False, + 'ticks': '', + 'title': '', + 'zeroline': False}, + 'yaxis': {'autorange': True, + 'mirror': False, + 'showgrid': False, + 'showline': False, + 'showticklabels': False, + 'ticklen': 4, + 'ticks': '', + 'title': '', + 'zeroline': False}}} + + self.assert_dict_equal(test_violin['data'][0], + exp_violin['data'][0]) + + self.assert_dict_equal(test_violin['data'][1], + exp_violin['data'][1]) + + self.assert_dict_equal(test_violin['layout'], + exp_violin['layout']) diff --git a/plotly/tools.py b/plotly/tools.py index c2cf7a394e1..910c53b2412 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -28,6 +28,26 @@ 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'] +PLOTLY_SCALES = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], + 'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'], + 'Greens': ['rgb(0,68,27)', 'rgb(247,252,245)'], + 'YlOrRd': ['rgb(128,0,38)', 'rgb(255,255,204)'], + 'Bluered': ['rgb(0,0,255)', 'rgb(255,0,0)'], + 'RdBu': ['rgb(5,10,172)', 'rgb(178,10,28)'], + 'Reds': ['rgb(220,220,220)', 'rgb(178,10,28)'], + 'Blues': ['rgb(5,10,172)', 'rgb(220,220,220)'], + 'Picnic': ['rgb(0,0,255)', 'rgb(255,0,0)'], + 'Rainbow': ['rgb(150,0,90)', 'rgb(255,0,0)'], + 'Portland': ['rgb(12,51,131)', 'rgb(217,30,30)'], + 'Jet': ['rgb(0,0,131)', 'rgb(128,0,0)'], + 'Hot': ['rgb(0,0,0)', 'rgb(255,255,255)'], + 'Blackbody': ['rgb(0,0,0)', 'rgb(160,200,255)'], + 'Earth': ['rgb(0,0,130)', 'rgb(255,255,255)'], + 'Electric': ['rgb(0,0,0)', 'rgb(255,250,220)'], + 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} + +# color constants for violin plot +DEFAULT_FILLCOLOR = '#1f77b4' DEFAULT_HISTNORM = 'probability density' ALTERNATIVE_HISTNORM = 'probability' @@ -1454,6 +1474,793 @@ class FigureFactory(object): more information and examples of a specific chart type. """ + @staticmethod + def _validate_colors(colors, colortype='tuple'): + """ + Validates color(s) and returns a list of color(s) of a specified type + """ + from numbers import Number + if colors is None: + colors = DEFAULT_PLOTLY_COLORS + + if isinstance(colors, str): + if colors in PLOTLY_SCALES: + colors = PLOTLY_SCALES[colors] + elif 'rgb' in colors or '#' in colors: + colors = [colors] + else: + raise exceptions.PlotlyError( + "If your colors variable is a string, it must be a " + "Plotly scale, an rgb color or a hex color.") + + elif isinstance(colors, tuple): + if isinstance(colors[0], Number): + colors = [colors] + else: + colors = list(colors) + + # convert color elements in list to tuple color + for j, each_color in enumerate(colors): + if 'rgb' in each_color: + each_color = FigureFactory._color_parser( + each_color, FigureFactory._unlabel_rgb + ) + for value in each_color: + if value > 255.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your rgb colors " + "tuples cannot exceed 255.0." + ) + each_color = FigureFactory._color_parser( + each_color, FigureFactory._unconvert_from_RGB_255 + ) + colors[j] = each_color + + if '#' in each_color: + each_color = FigureFactory._color_parser( + each_color, FigureFactory._hex_to_rgb + ) + each_color = FigureFactory._color_parser( + each_color, FigureFactory._unconvert_from_RGB_255 + ) + + colors[j] = each_color + + if isinstance(each_color, tuple): + for value in each_color: + if value > 1.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your colors tuples " + "cannot exceed 1.0." + ) + colors[j] = each_color + + if colortype == 'rgb': + for j, each_color in enumerate(colors): + rgb_color = FigureFactory._color_parser( + each_color, FigureFactory._convert_to_RGB_255 + ) + colors[j] = FigureFactory._color_parser( + rgb_color, FigureFactory._label_rgb + ) + + return colors + + @staticmethod + def _validate_colors_dict(colors, colortype='tuple'): + """ + Validates dictioanry of color(s) + """ + # validate each color element in the dictionary + for key in colors: + if 'rgb' in colors[key]: + colors[key] = FigureFactory._color_parser( + colors[key], FigureFactory._unlabel_rgb + ) + for value in colors[key]: + if value > 255.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your rgb colors " + "tuples cannot exceed 255.0." + ) + colors[key] = FigureFactory._color_parser( + colors[key], FigureFactory._unconvert_from_RGB_255 + ) + + if '#' in colors[key]: + colors[key] = FigureFactory._color_parser( + colors[key], FigureFactory._hex_to_rgb + ) + colors[key] = FigureFactory._color_parser( + colors[key], FigureFactory._unconvert_from_RGB_255 + ) + + if isinstance(colors[key], tuple): + for value in colors[key]: + if value > 1.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your colors tuples " + "cannot exceed 1.0." + ) + + if colortype == 'rgb': + for key in colors: + colors[key] = FigureFactory._color_parser( + colors[key], FigureFactory._convert_to_RGB_255 + ) + colors[key] = FigureFactory._color_parser( + colors[key], FigureFactory._label_rgb + ) + + return colors + + @staticmethod + def _calc_stats(data): + """ + Calculate statistics for use in violin plot. + """ + import numpy as np + + x = np.asarray(data, np.float) + vals_min = np.min(x) + vals_max = np.max(x) + q2 = np.percentile(x, 50, interpolation='linear') + q1 = np.percentile(x, 25, interpolation='lower') + q3 = np.percentile(x, 75, interpolation='higher') + iqr = q3 - q1 + whisker_dist = 1.5 * iqr + + # in order to prevent drawing whiskers outside the interval + # of data one defines the whisker positions as: + d1 = np.min(x[x >= (q1 - whisker_dist)]) + d2 = np.max(x[x <= (q3 + whisker_dist)]) + return { + 'min': vals_min, + 'max': vals_max, + 'q1': q1, + 'q2': q2, + 'q3': q3, + 'd1': d1, + 'd2': d2 + } + + @staticmethod + def _make_half_violin(x, y, fillcolor='#1f77b4', + linecolor='rgb(0, 0, 0)'): + """ + Produces a sideways probability distribution fig violin plot. + """ + from plotly.graph_objs import graph_objs + + text = ['(pdf(y), y)=(' + '{:0.2f}'.format(x[i]) + + ', ' + '{:0.2f}'.format(y[i]) + ')' + for i in range(len(x))] + + return graph_objs.Scatter( + x=x, + y=y, + mode='lines', + name='', + text=text, + fill='tonextx', + fillcolor=fillcolor, + line=graph_objs.Line(width=0.5, color=linecolor, shape='spline'), + hoverinfo='text', + opacity=0.5 + ) + + @staticmethod + def _make_violin_rugplot(vals, pdf_max, distance, + color='#1f77b4'): + """ + Returns a rugplot fig for a violin plot. + """ + from plotly.graph_objs import graph_objs + + return graph_objs.Scatter( + y=vals, + x=[-pdf_max-distance]*len(vals), + marker=graph_objs.Marker( + color=color, + symbol='line-ew-open' + ), + mode='markers', + name='', + showlegend=False, + hoverinfo='y' + ) + + @staticmethod + def _make_quartiles(q1, q3): + """ + Makes the upper and lower quartiles for a violin plot. + """ + from plotly.graph_objs import graph_objs + + return graph_objs.Scatter( + x=[0, 0], + y=[q1, q3], + text=['lower-quartile: ' + '{:0.2f}'.format(q1), + 'upper-quartile: ' + '{:0.2f}'.format(q3)], + mode='lines', + line=graph_objs.Line( + width=4, + color='rgb(0,0,0)' + ), + hoverinfo='text' + ) + + @staticmethod + def _make_median(q2): + """ + Formats the 'median' hovertext for a violin plot. + """ + from plotly.graph_objs import graph_objs + + return graph_objs.Scatter( + x=[0], + y=[q2], + text=['median: ' + '{:0.2f}'.format(q2)], + mode='markers', + marker=dict(symbol='square', + color='rgb(255,255,255)'), + hoverinfo='text' + ) + + @staticmethod + def _make_non_outlier_interval(d1, d2): + """ + Returns the scatterplot fig of most of a violin plot. + """ + from plotly.graph_objs import graph_objs + + return graph_objs.Scatter( + x=[0, 0], + y=[d1, d2], + name='', + mode='lines', + line=graph_objs.Line(width=1.5, + color='rgb(0,0,0)') + ) + + @staticmethod + def _make_XAxis(xaxis_title, xaxis_range): + """ + Makes the x-axis for a violin plot. + """ + from plotly.graph_objs import graph_objs + + xaxis = graph_objs.XAxis(title=xaxis_title, + range=xaxis_range, + showgrid=False, + zeroline=False, + showline=False, + mirror=False, + ticks='', + showticklabels=False, + ) + return xaxis + + @staticmethod + def _make_YAxis(yaxis_title): + """ + Makes the y-axis for a violin plot. + """ + from plotly.graph_objs import graph_objs + + yaxis = graph_objs.YAxis(title=yaxis_title, + showticklabels=True, + autorange=True, + ticklen=4, + showline=True, + zeroline=False, + showgrid=False, + mirror=False) + return yaxis + + @staticmethod + def _violinplot(vals, fillcolor='#1f77b4', rugplot=True): + """ + Refer to FigureFactory.create_violin() for docstring. + """ + import numpy as np + from scipy import stats + + vals = np.asarray(vals, np.float) + # summary statistics + vals_min = FigureFactory._calc_stats(vals)['min'] + vals_max = FigureFactory._calc_stats(vals)['max'] + q1 = FigureFactory._calc_stats(vals)['q1'] + q2 = FigureFactory._calc_stats(vals)['q2'] + q3 = FigureFactory._calc_stats(vals)['q3'] + d1 = FigureFactory._calc_stats(vals)['d1'] + d2 = FigureFactory._calc_stats(vals)['d2'] + + # kernel density estimation of pdf + pdf = stats.gaussian_kde(vals) + # grid over the data interval + xx = np.linspace(vals_min, vals_max, 100) + # evaluate the pdf at the grid xx + yy = pdf(xx) + max_pdf = np.max(yy) + # distance from the violin plot to rugplot + distance = (2.0 * max_pdf)/10 if rugplot else 0 + # range for x values in the plot + plot_xrange = [-max_pdf - distance - 0.1, max_pdf + 0.1] + plot_data = [FigureFactory._make_half_violin( + -yy, xx, fillcolor=fillcolor), + FigureFactory._make_half_violin( + yy, xx, fillcolor=fillcolor), + FigureFactory._make_non_outlier_interval(d1, d2), + FigureFactory._make_quartiles(q1, q3), + FigureFactory._make_median(q2)] + if rugplot: + plot_data.append(FigureFactory._make_violin_rugplot( + vals, + max_pdf, + distance=distance, + color=fillcolor) + ) + return plot_data, plot_xrange + + @staticmethod + def _violin_no_colorscale(data, data_header, group_header, colors, + use_colorscale, group_stats, + height, width, title): + """ + Refer to FigureFactory.create_violin() for docstring. + + Returns fig for violin plot without colorscale. + + """ + from plotly.graph_objs import graph_objs + import numpy as np + + # collect all group names + group_name = [] + for name in data[group_header]: + if name not in group_name: + group_name.append(name) + group_name.sort() + + gb = data.groupby([group_header]) + L = len(group_name) + + fig = make_subplots(rows=1, cols=L, + shared_yaxes=True, + horizontal_spacing=0.025, + print_grid=True) + color_index = 0 + for k, gr in enumerate(group_name): + vals = np.asarray(gb.get_group(gr)[data_header], np.float) + if color_index >= len(colors): + color_index = 0 + plot_data, plot_xrange = FigureFactory._violinplot( + vals, + fillcolor=colors[color_index] + ) + layout = graph_objs.Layout() + + for item in plot_data: + fig.append_trace(item, 1, k + 1) + color_index += 1 + + # add violin plot labels + fig['layout'].update({'xaxis{}'.format(k + 1): + FigureFactory._make_XAxis(group_name[k], + plot_xrange)}) + + # set the sharey axis style + fig['layout'].update( + {'yaxis{}'.format(1): FigureFactory._make_YAxis('')} + ) + fig['layout'].update( + title=title, + showlegend=False, + hovermode='closest', + autosize=False, + height=height, + width=width + ) + + return fig + + @staticmethod + def _violin_colorscale(data, data_header, group_header, colors, + use_colorscale, group_stats, height, width, title): + """ + Refer to FigureFactory.create_violin() for docstring. + + Returns fig for violin plot with colorscale. + + """ + from plotly.graph_objs import graph_objs + import numpy as np + + # collect all group names + group_name = [] + for name in data[group_header]: + if name not in group_name: + group_name.append(name) + group_name.sort() + + # make sure all group names are keys in group_stats + for group in group_name: + if group not in group_stats: + raise exceptions.PlotlyError("All values/groups in the index " + "column must be represented " + "as a key in group_stats.") + + gb = data.groupby([group_header]) + L = len(group_name) + + fig = make_subplots(rows=1, cols=L, + shared_yaxes=True, + horizontal_spacing=0.025, + print_grid=True) + + # prepare low and high color for colorscale + lowcolor = FigureFactory._color_parser( + colors[0], FigureFactory._unlabel_rgb + ) + highcolor = FigureFactory._color_parser( + colors[1], FigureFactory._unlabel_rgb + ) + + # find min and max values in group_stats + group_stats_values = [] + for key in group_stats: + group_stats_values.append(group_stats[key]) + + max_value = max(group_stats_values) + min_value = min(group_stats_values) + + for k, gr in enumerate(group_name): + vals = np.asarray(gb.get_group(gr)[data_header], np.float) + + # find intermediate color from colorscale + intermed = (group_stats[gr] - min_value) / (max_value - min_value) + intermed_color = FigureFactory._find_intermediate_color( + lowcolor, highcolor, intermed + ) + + plot_data, plot_xrange = FigureFactory._violinplot( + vals, + fillcolor='rgb{}'.format(intermed_color) + ) + layout = graph_objs.Layout() + + for item in plot_data: + fig.append_trace(item, 1, k + 1) + fig['layout'].update({'xaxis{}'.format(k + 1): + FigureFactory._make_XAxis(group_name[k], + plot_xrange)}) + # add colorbar to plot + trace_dummy = graph_objs.Scatter( + x=[0], + y=[0], + mode='markers', + marker=dict( + size=2, + cmin=min_value, + cmax=max_value, + colorscale=[[0, colors[0]], + [1, colors[1]]], + showscale=True), + showlegend=False, + ) + fig.append_trace(trace_dummy, 1, L) + + # set the sharey axis style + fig['layout'].update( + {'yaxis{}'.format(1): FigureFactory._make_YAxis('')} + ) + fig['layout'].update( + title=title, + showlegend=False, + hovermode='closest', + autosize=False, + height=height, + width=width + ) + + return fig + + @staticmethod + def _violin_dict(data, data_header, group_header, colors, use_colorscale, + group_stats, height, width, title): + """ + Refer to FigureFactory.create_violin() for docstring. + + Returns fig for violin plot without colorscale. + + """ + from plotly.graph_objs import graph_objs + import numpy as np + + # collect all group names + group_name = [] + for name in data[group_header]: + if name not in group_name: + group_name.append(name) + group_name.sort() + + # check if all group names appear in colors dict + for group in group_name: + if group not in colors: + raise exceptions.PlotlyError("If colors is a dictionary, all " + "the group names must appear as " + "keys in colors.") + + gb = data.groupby([group_header]) + L = len(group_name) + + fig = make_subplots(rows=1, cols=L, + shared_yaxes=True, + horizontal_spacing=0.025, + print_grid=True) + + for k, gr in enumerate(group_name): + vals = np.asarray(gb.get_group(gr)[data_header], np.float) + plot_data, plot_xrange = FigureFactory._violinplot( + vals, + fillcolor=colors[gr] + ) + layout = graph_objs.Layout() + + for item in plot_data: + fig.append_trace(item, 1, k + 1) + + # add violin plot labels + fig['layout'].update({'xaxis{}'.format(k + 1): + FigureFactory._make_XAxis(group_name[k], + plot_xrange)}) + + # set the sharey axis style + fig['layout'].update( + {'yaxis{}'.format(1): FigureFactory._make_YAxis('')} + ) + fig['layout'].update( + title=title, + showlegend=False, + hovermode='closest', + autosize=False, + height=height, + width=width + ) + + return fig + + @staticmethod + def create_violin(data, data_header=None, group_header=None, + colors=None, use_colorscale=False, group_stats=None, + height=450, width=600, title='Violin and Rug Plot'): + """ + Returns figure for a violin plot + + :param (list|array) data: accepts either a list of numerical values, + a list of dictionaries all with identical keys and at least one + column of numeric values, or a pandas dataframe with at least one + column of numbers + :param (str) data_header: the header of the data column to be used + from an inputted pandas dataframe. Not applicable if 'data' is + a list of numeric values + :param (str) group_header: applicable if grouping data by a variable. + 'group_header' must be set to the name of the grouping variable. + :param (str|tuple|list|dict) colors: either a plotly scale name, + an rgb or hex color, a color tuple, a list of colors or a + dictionary. An rgb color is of the form 'rgb(x, y, z)' where + x, y and z belong to the interval [0, 255] and a color tuple is a + tuple of the form (a, b, c) where a, b and c belong to [0, 1]. + If colors is a list, it must contain valid color types as its + members. + :param (bool) use_colorscale: Only applicable if grouping by another + variable. Will implement a colorscale based on the first 2 colors + of param colors. This means colors must be a list with at least 2 + colors in it (Plotly colorscales are accepted since they map to a + list of two rgb colors) + :param (dict) group_stats: a dictioanry where each key is a unique + value from the group_header column in data. Each value must be a + number and will be used to color the violin plots if a colorscale + is being used + :param (float) height: the height of the violin plot + :param (float) width: the width of the violin plot + :param (str) title: the title of the violin plot + + Example 1: Single Violin Plot + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + from plotly.graph_objs import graph_objs + + import numpy as np + from scipy import stats + + # create list of random values + data_list = np.random.randn(100) + data_list.tolist() + + # create violin fig + fig = FF.create_violin(data_list, colors='#604d9e') + + # plot + py.iplot(fig, filename='Violin Plot') + ``` + + Example 2: Multiple Violin Plots with Qualitative Coloring + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + from plotly.graph_objs import graph_objs + + import numpy as np + import pandas as pd + from scipy import stats + + # create dataframe + np.random.seed(619517) + Nr=250 + y = np.random.randn(Nr) + gr = np.random.choice(list("ABCDE"), Nr) + norm_params=[(0, 1.2), (0.7, 1), (-0.5, 1.4), (0.3, 1), (0.8, 0.9)] + + for i, letter in enumerate("ABCDE"): + y[gr == letter] *=norm_params[i][1]+ norm_params[i][0] + df = pd.DataFrame(dict(Score=y, Group=gr)) + + # create violin fig + fig = FF.create_violin(df, data_header='Score', group_header='Group', + height=600, width=1000) + + # plot + py.iplot(fig, filename='Violin Plot with Coloring') + ``` + + Example 3: Violin Plots with Colorscale + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + from plotly.graph_objs import graph_objs + + import numpy as np + import pandas as pd + from scipy import stats + + # create dataframe + np.random.seed(619517) + Nr=250 + y = np.random.randn(Nr) + gr = np.random.choice(list("ABCDE"), Nr) + norm_params=[(0, 1.2), (0.7, 1), (-0.5, 1.4), (0.3, 1), (0.8, 0.9)] + + for i, letter in enumerate("ABCDE"): + y[gr == letter] *=norm_params[i][1]+ norm_params[i][0] + df = pd.DataFrame(dict(Score=y, Group=gr)) + + # define header params + data_header = 'Score' + group_header = 'Group' + + # make groupby object with pandas + groupby_data = df.groupby([group_header]) + + for group in "ABCDE": + data_from_group = groupby_data.get_group(group)[data_header] + # take a stat of the grouped data + stat = np.median(data_from_group) + # add to dictionary + group_stats[group] = stat + + # create violin fig + fig = FF.create_violin(df, data_header='Score', group_header='Group', + height=600, width=1000, use_colorscale=True, + group_stats=group_stats) + + # plot + py.iplot(fig, filename='Violin Plot with Colorscale') + ``` + """ + from plotly.graph_objs import graph_objs + from numbers import Number + + # Validate colors + if isinstance(colors, dict): + valid_colors = FigureFactory._validate_colors_dict(colors, 'rgb') + else: + valid_colors = FigureFactory._validate_colors(colors, 'rgb') + + # validate data and choose plot type + if group_header is None: + if isinstance(data, list): + if len(data) <= 0: + raise exceptions.PlotlyError("If data is a list, it must be " + "nonempty and contain either " + "numbers or dictionaries.") + + if not all(isinstance(element, Number) for element in data): + raise exceptions.PlotlyError("If data is a list, it must " + "contain only numbers.") + + if _pandas_imported and isinstance(data, pd.core.frame.DataFrame): + if data_header is None: + raise exceptions.PlotlyError("data_header must be the " + "column name with the " + "desired numeric data for " + "the violin plot.") + + data = data[data_header].values.tolist() + + # call the plotting functions + plot_data, plot_xrange = FigureFactory._violinplot( + data, fillcolor=valid_colors[0] + ) + + layout = graph_objs.Layout( + title=title, + autosize=False, + font=graph_objs.Font(size=11), + height=height, + showlegend=False, + width=width, + xaxis=FigureFactory._make_XAxis('', plot_xrange), + yaxis=FigureFactory._make_YAxis(''), + hovermode='closest' + ) + layout['yaxis'].update(dict(showline=False, + showticklabels=False, + ticks='')) + + fig = graph_objs.Figure(data=graph_objs.Data(plot_data), + layout=layout) + + return fig + + else: + if not isinstance(data, pd.core.frame.DataFrame): + raise exceptions.PlotlyError("Error. You must use a pandas " + "DataFrame if you are using a " + "group header.") + + if data_header is None: + raise exceptions.PlotlyError("data_header must be the column " + "name with the desired numeric " + "data for the violin plot.") + + if use_colorscale is False: + if isinstance(valid_colors, dict): + # validate colors dict choice below + fig = FigureFactory._violin_dict( + data, data_header, group_header, valid_colors, + use_colorscale, group_stats, height, width, title + ) + return fig + else: + fig = FigureFactory._violin_no_colorscale( + data, data_header, group_header, valid_colors, + use_colorscale, group_stats, height, width, title + ) + return fig + else: + if isinstance(valid_colors, dict): + raise exceptions.PlotlyError("The colors param cannot be " + "a dictionary if you are " + "using a colorscale.") + + if len(valid_colors) < 2: + raise exceptions.PlotlyError("colors must be a list with " + "at least 2 colors. A " + "Plotly scale is allowed.") + + if not isinstance(group_stats, dict): + raise exceptions.PlotlyError("Your group_stats param " + "must be a dictionary.") + + fig = FigureFactory._violin_colorscale( + data, data_header, group_header, valid_colors, + use_colorscale, group_stats, height, width, title + ) + return fig + @staticmethod def _find_intermediate_color(lowcolor, highcolor, intermed): """ @@ -1473,6 +2280,34 @@ def _find_intermediate_color(lowcolor, highcolor, intermed): lowcolor[2] + intermed * diff_2) return inter_colors + @staticmethod + def _color_parser(colors, function): + """ + Takes color(s) and a function and applys the function on the color(s) + + In particular, this function identifies whether the given color object + is an iterable or not and applies the given color-parsing function to + the color or iterable of colors. If given an iterable, it will only be + able to work with it if all items in the iterable are of the same type + - rgb string, hex string or tuple + + """ + from numbers import Number + if isinstance(colors, str): + return function(colors) + + if isinstance(colors, tuple) and isinstance(colors[0], Number): + return function(colors) + + if hasattr(colors, '__iter__'): + if isinstance(colors, tuple): + new_color_tuple = tuple(function(item) for item in colors) + return new_color_tuple + + else: + new_color_list = [function(item) for item in colors] + return new_color_list + @staticmethod def _unconvert_from_RGB_255(colors): """ @@ -1483,24 +2318,11 @@ def _unconvert_from_RGB_255(colors): a value between 0 and 1 """ - if isinstance(colors, tuple): - - un_rgb_color = (colors[0]/(255.0), - colors[1]/(255.0), - colors[2]/(255.0)) - - return un_rgb_color - - if isinstance(colors, list): - un_rgb_colors = [] - for color in colors: - un_rgb_color = (color[0]/(255.0), - color[1]/(255.0), - color[2]/(255.0)) - - un_rgb_colors.append(un_rgb_color) + un_rgb_color = (colors[0]/(255.0), + colors[1]/(255.0), + colors[2]/(255.0)) - return un_rgb_colors + return un_rgb_color @staticmethod def _map_face2color(face, colormap, vmin, vmax): @@ -1887,106 +2709,9 @@ def dist_origin(x, y, z): ``` """ from plotly.graph_objs import graph_objs - plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], - 'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'], - 'Greens': ['rgb(0,68,27)', 'rgb(247,252,245)'], - 'YlOrRd': ['rgb(128,0,38)', 'rgb(255,255,204)'], - 'Bluered': ['rgb(0,0,255)', 'rgb(255,0,0)'], - 'RdBu': ['rgb(5,10,172)', 'rgb(178,10,28)'], - 'Reds': ['rgb(220,220,220)', 'rgb(178,10,28)'], - 'Blues': ['rgb(5,10,172)', 'rgb(220,220,220)'], - 'Picnic': ['rgb(0,0,255)', 'rgb(255,0,0)'], - 'Rainbow': ['rgb(150,0,90)', 'rgb(255,0,0)'], - 'Portland': ['rgb(12,51,131)', 'rgb(217,30,30)'], - 'Jet': ['rgb(0,0,131)', 'rgb(128,0,0)'], - 'Hot': ['rgb(0,0,0)', 'rgb(255,255,255)'], - 'Blackbody': ['rgb(0,0,0)', 'rgb(160,200,255)'], - 'Earth': ['rgb(0,0,130)', 'rgb(255,255,255)'], - 'Electric': ['rgb(0,0,0)', 'rgb(255,250,220)'], - 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} # Validate colormap - if colormap is None: - colormap = [DEFAULT_PLOTLY_COLORS[0], - DEFAULT_PLOTLY_COLORS[1]] - colormap = FigureFactory._unlabel_rgb(colormap) - colormap = FigureFactory._unconvert_from_RGB_255(colormap) - - if isinstance(colormap, str): - if colormap in plotly_scales: - colormap = plotly_scales[colormap] - colormap = FigureFactory._unlabel_rgb(colormap) - colormap = FigureFactory._unconvert_from_RGB_255(colormap) - - elif 'rgb' in colormap: - # put colormap in list - colors_list = [] - colors_list.append(colormap) - colormap = colors_list - - colormap = FigureFactory._unlabel_rgb(colormap) - colormap = FigureFactory._unconvert_from_RGB_255(colormap) - - elif '#' in colormap: - colormap = FigureFactory._hex_to_rgb(colormap) - colormap = FigureFactory._unconvert_from_RGB_255(colormap) - - # put colormap in list - colors_list = [] - colors_list.append(colormap) - colormap = colors_list - - else: - scale_keys = list(plotly_scales.keys()) - raise exceptions.PlotlyError("If you input a string " - "for 'colormap', it must " - "either be a Plotly " - "colorscale, an 'rgb' " - "color or a hex color." - "Valid plotly colorscale " - "names are {}".format(scale_keys)) - elif isinstance(colormap, tuple): - colors_list = [] - colors_list.append(colormap) - colormap = colors_list - - elif isinstance(colormap, list): - new_colormap = [] - for color in colormap: - if 'rgb' in color: - color = FigureFactory._unlabel_rgb(color) - - for value in color: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colormap " - "tuples cannot " - "exceed 255.0.") - - color = FigureFactory._unconvert_from_RGB_255(color) - new_colormap.append(color) - elif '#' in color: - color = FigureFactory._hex_to_rgb(color) - color = FigureFactory._unconvert_from_RGB_255(color) - new_colormap.append(color) - elif isinstance(color, tuple): - - for value in color: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colormap " - "tuples cannot " - "exceed 1.0.") - new_colormap.append(color) - colormap = new_colormap - - else: - raise exceptions.PlotlyError("You must input a valid colormap. " - "Valid types include a plotly scale, " - "rgb, hex or tuple color, or lastly " - "a list of any color types.") + colormap = FigureFactory._validate_colors(colormap, 'tuple') data1 = FigureFactory._trisurf(x, y, z, simplices, color_func=color_func, @@ -2277,11 +3002,16 @@ def _scatterplot_theme(dataframe, headers, diag, size, height, # Convert colormap to list of n RGB tuples if colormap_type == 'seq': - foo = FigureFactory._unlabel_rgb(colormap) + foo = FigureFactory._color_parser( + colormap, FigureFactory._unlabel_rgb + ) foo = FigureFactory._n_colors(foo[0], foo[1], n_colors_len) - theme = FigureFactory._label_rgb(foo) + theme = FigureFactory._color_parser( + foo, FigureFactory._label_rgb + ) + if colormap_type == 'cat': # leave list of colors the same way theme = colormap @@ -2446,11 +3176,16 @@ def _scatterplot_theme(dataframe, headers, diag, size, height, # Convert colormap to list of n RGB tuples if colormap_type == 'seq': - foo = FigureFactory._unlabel_rgb(colormap) + foo = FigureFactory._color_parser( + colormap, FigureFactory._unlabel_rgb + ) foo = FigureFactory._n_colors(foo[0], foo[1], len(intervals)) - theme = FigureFactory._label_rgb(foo) + theme = FigureFactory._color_parser( + foo, FigureFactory._label_rgb + ) + if colormap_type == 'cat': # leave list of colors the same way theme = colormap @@ -2904,22 +3639,10 @@ def _endpts_to_intervals(endpts): @staticmethod def _convert_to_RGB_255(colors): """ - Return a (list of) tuple(s) where each element is multiplied by 255 - - Takes a tuple or a list of tuples where each element of each tuple is - between 0 and 1. Returns the same tuple(s) where each tuple element is - multiplied by 255 + Multiplies each element of a triplet by 255 """ - if isinstance(colors, tuple): - return (colors[0]*255.0, colors[1]*255.0, colors[2]*255.0) - - else: - colors_255 = [] - for color in colors: - rgb_color = (color[0]*255.0, color[1]*255.0, color[2]*255.0) - colors_255.append(rgb_color) - return colors_255 + return (colors[0]*255.0, colors[1]*255.0, colors[2]*255.0) @staticmethod def _n_colors(lowcolor, highcolor, n_colors): @@ -2950,24 +3673,9 @@ def _n_colors(lowcolor, highcolor, n_colors): @staticmethod def _label_rgb(colors): """ - Takes tuple(s) (a, b, c) and returns rgb color(s) 'rgb(a, b, c)' - - Takes either a list or a single color tuple of the form (a, b, c) and - returns the same color(s) with each tuple replaced by a string - 'rgb(a, b, c)' - + Takes tuple (a, b, c) and returns an rgb color 'rgb(a, b, c)' """ - if isinstance(colors, tuple): - return ('rgb(%s, %s, %s)' % (colors[0], colors[1], colors[2])) - else: - colors_label = [] - for color in colors: - color_label = ('rgb(%s, %s, %s)' % (color[0], - color[1], - color[2])) - colors_label.append(color_label) - - return colors_label + return ('rgb(%s, %s, %s)' % (colors[0], colors[1], colors[2])) @staticmethod def _unlabel_rgb(colors): @@ -2978,52 +3686,25 @@ def _unlabel_rgb(colors): such colors and returns the color tuples in tuple(s) (a, b, c) """ - if isinstance(colors, str): - str_vals = '' - for index in range(len(colors)): - try: - float(colors[index]) + str_vals = '' + for index in range(len(colors)): + try: + float(colors[index]) + str_vals = str_vals + colors[index] + except ValueError: + if colors[index] == ',' or colors[index] == '.': str_vals = str_vals + colors[index] - except ValueError: - if (colors[index] == ',') or (colors[index] == '.'): - str_vals = str_vals + colors[index] - - str_vals = str_vals + ',' - numbers = [] - str_num = '' - for char in str_vals: - if char != ',': - str_num = str_num + char - else: - numbers.append(float(str_num)) - str_num = '' - return (numbers[0], numbers[1], numbers[2]) - - if isinstance(colors, list): - unlabelled_colors = [] - for color in colors: - str_vals = '' - for index in range(len(color)): - try: - float(color[index]) - str_vals = str_vals + color[index] - except ValueError: - if (color[index] == ',') or (color[index] == '.'): - str_vals = str_vals + color[index] - - str_vals = str_vals + ',' - numbers = [] - str_num = '' - for char in str_vals: - if char != ',': - str_num = str_num + char - else: - numbers.append(float(str_num)) - str_num = '' - unlabelled_tuple = (numbers[0], numbers[1], numbers[2]) - unlabelled_colors.append(unlabelled_tuple) - return unlabelled_colors + str_vals = str_vals + ',' + numbers = [] + str_num = '' + for char in str_vals: + if char != ',': + str_num = str_num + char + else: + numbers.append(float(str_num)) + str_num = '' + return (numbers[0], numbers[1], numbers[2]) @staticmethod def create_scatterplotmatrix(df, index=None, endpts=None, diag='scatter', @@ -3235,154 +3916,16 @@ def create_scatterplotmatrix(df, index=None, endpts=None, diag='scatter', headers = [] if index_vals is None: index_vals = [] - plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], - 'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'], - 'Greens': ['rgb(0,68,27)', 'rgb(247,252,245)'], - 'YlOrRd': ['rgb(128,0,38)', 'rgb(255,255,204)'], - 'Bluered': ['rgb(0,0,255)', 'rgb(255,0,0)'], - 'RdBu': ['rgb(5,10,172)', 'rgb(178,10,28)'], - 'Reds': ['rgb(220,220,220)', 'rgb(178,10,28)'], - 'Blues': ['rgb(5,10,172)', 'rgb(220,220,220)'], - 'Picnic': ['rgb(0,0,255)', 'rgb(255,0,0)'], - 'Rainbow': ['rgb(150,0,90)', 'rgb(255,0,0)'], - 'Portland': ['rgb(12,51,131)', 'rgb(217,30,30)'], - 'Jet': ['rgb(0,0,131)', 'rgb(128,0,0)'], - 'Hot': ['rgb(0,0,0)', 'rgb(255,255,255)'], - 'Blackbody': ['rgb(0,0,0)', 'rgb(160,200,255)'], - 'Earth': ['rgb(0,0,130)', 'rgb(255,255,255)'], - 'Electric': ['rgb(0,0,0)', 'rgb(255,250,220)'], - 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} FigureFactory._validate_scatterplotmatrix(df, index, diag, colormap_type, **kwargs) # Validate colormap - if colormap is None: - colormap = DEFAULT_PLOTLY_COLORS - - if isinstance(colormap, str): - if colormap in plotly_scales: - colormap = plotly_scales[colormap] - - elif 'rgb' in colormap: - colormap = FigureFactory._unlabel_rgb(colormap) - for value in colormap: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colormap " - "tuples cannot " - "exceed 255.0.") - colormap = FigureFactory._label_rgb(colormap) - - # put colormap in list - colors_list = [] - colors_list.append(colormap) - colormap = colors_list - - elif '#' in colormap: - colormap = FigureFactory._hex_to_rgb(colormap) - colormap = FigureFactory._label_rgb(colormap) - - # put colormap in list - colors_list = [] - colors_list.append(colormap) - colormap = colors_list - - else: - scale_keys = list(plotly_scales.keys()) - raise exceptions.PlotlyError("If you input a string " - "for 'colormap', it must " - "either be a Plotly " - "colorscale, an 'rgb' " - "color or a hex color." - "Valid plotly colorscale " - "names are {}".format(scale_keys)) - elif isinstance(colormap, tuple): - for value in colormap: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colormap " - "tuples cannot " - "exceed 1.0.") - - colors_list = [] - colors_list.append(colormap) - colormap = colors_list - - colormap = FigureFactory._convert_to_RGB_255(colormap) - colormap = FigureFactory._label_rgb(colormap) - - elif isinstance(colormap, list): - new_colormap = [] - for color in colormap: - if 'rgb' in color: - color = FigureFactory._unlabel_rgb(color) - - for value in color: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colormap " - "tuples cannot " - "exceed 255.0.") - - color = FigureFactory._label_rgb(color) - new_colormap.append(color) - elif '#' in color: - color = FigureFactory._hex_to_rgb(color) - color = FigureFactory._label_rgb(color) - new_colormap.append(color) - elif isinstance(color, tuple): - for value in color: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colormap " - "tuples cannot " - "exceed 1.0.") - color = FigureFactory._convert_to_RGB_255(color) - color = FigureFactory._label_rgb(color) - new_colormap.append(color) - colormap = new_colormap - - elif isinstance(colormap, dict): - for name in colormap: - if 'rgb' in colormap[name]: - color = FigureFactory._unlabel_rgb(colormap[name]) - for value in color: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colormap " - "tuples cannot " - "exceed 255.0.") - - elif '#' in colormap[name]: - color = FigureFactory._hex_to_rgb(colormap[name]) - color = FigureFactory._label_rgb(color) - colormap[name] = color - - elif isinstance(colormap[name], tuple): - for value in colormap[name]: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colormap " - "tuples cannot " - "exceed 1.0.") - color = FigureFactory._convert_to_RGB_255(colormap[name]) - color = FigureFactory._label_rgb(color) - colormap[name] = color - + if isinstance(colormap, dict): + colormap = FigureFactory._validate_colors_dict(colormap, 'rgb') else: - raise exceptions.PlotlyError("You must input a valid colormap. " - "Valid types include a plotly scale, " - "rgb, hex or tuple color, a list of " - "any color types, or a dictionary " - "with index names each assigned " - "to a color.") + colormap = FigureFactory._validate_colors(colormap, 'rgb') + if not index: for name in df: headers.append(name) @@ -3421,29 +3964,19 @@ def create_scatterplotmatrix(df, index=None, endpts=None, diag='scatter', "dictionary, all the " "names in the index " "must be keys.") - - figure = FigureFactory._scatterplot_dict(dataframe, - headers, - diag, - size, height, - width, title, - index, - index_vals, - endpts, - colormap, - colormap_type, - **kwargs) + figure = FigureFactory._scatterplot_dict( + dataframe, headers, diag, size, height, width, title, + index, index_vals, endpts, colormap, colormap_type, + **kwargs + ) return figure else: - figure = FigureFactory._scatterplot_theme(dataframe, headers, - diag, size, - height, width, - title, index, - index_vals, - endpts, colormap, - colormap_type, - **kwargs) + figure = FigureFactory._scatterplot_theme( + dataframe, headers, diag, size, height, width, title, + index, index_vals, endpts, colormap, colormap_type, + **kwargs + ) return figure @staticmethod