diff --git a/docs/tutorials/data_visualization.ipynb b/docs/tutorials/data_visualization.ipynb index 739dd1d05..ca1c3ed5f 100644 --- a/docs/tutorials/data_visualization.ipynb +++ b/docs/tutorials/data_visualization.ipynb @@ -210,6 +210,26 @@ } } }, + { + "cell_type": "markdown", + "source": [ + "## Histogram of all columns" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "titanic_numerical.plot_histograms()" + ], + "metadata": { + "collapsed": false + } + }, { "cell_type": "markdown", "source": [ diff --git a/src/safeds/data/tabular/containers/_table.py b/src/safeds/data/tabular/containers/_table.py index c05d47c1a..747b1b32c 100644 --- a/src/safeds/data/tabular/containers/_table.py +++ b/src/safeds/data/tabular/containers/_table.py @@ -1313,6 +1313,35 @@ def plot_scatterplot(self, x_column_name: str, y_column_name: str) -> Image: buffer.seek(0) return Image(buffer, format_=ImageFormat.PNG) + def plot_histograms(self) -> Image: + """ + Plot a histogram for every column. + + Returns + ------- + plot: Image + The plot as an image. + """ + col_wrap = min(self.number_of_columns, 3) + + data = pd.melt(self._data, value_vars=self.column_names) + grid = sns.FacetGrid(data=data, col="variable", col_wrap=col_wrap, sharex=False, sharey=False) + grid.map(sns.histplot, "value") + grid.set_xlabels("") + grid.set_ylabels("") + grid.set_titles("{col_name}") + for axes in grid.axes.flat: + axes.set_xticks(axes.get_xticks()) + axes.set_xticklabels(axes.get_xticklabels(), rotation=45, horizontalalignment="right") + grid.tight_layout() + fig = grid.fig + + buffer = io.BytesIO() + fig.savefig(buffer, format="png") + plt.close() + buffer.seek(0) + return Image(buffer, ImageFormat.PNG) + # ------------------------------------------------------------------------------------------------------------------ # Conversion # ------------------------------------------------------------------------------------------------------------------ diff --git a/tests/resources/image/snapshot_histograms/four_columns.png b/tests/resources/image/snapshot_histograms/four_columns.png new file mode 100644 index 000000000..45e050640 Binary files /dev/null and b/tests/resources/image/snapshot_histograms/four_columns.png differ diff --git a/tests/resources/image/snapshot_histograms/one_column.png b/tests/resources/image/snapshot_histograms/one_column.png new file mode 100644 index 000000000..b9cf7f9a6 Binary files /dev/null and b/tests/resources/image/snapshot_histograms/one_column.png differ diff --git a/tests/safeds/data/tabular/containers/_column/test_plot_boxplot.py b/tests/safeds/data/tabular/containers/_column/test_plot_boxplot.py index 8d9212f59..84c0afe87 100644 --- a/tests/safeds/data/tabular/containers/_column/test_plot_boxplot.py +++ b/tests/safeds/data/tabular/containers/_column/test_plot_boxplot.py @@ -11,7 +11,10 @@ def test_should_match_snapshot() -> None: table.get_column("A").plot_boxplot() current = table.get_column("A").plot_boxplot() snapshot = Image.from_png_file(resolve_resource_path("./image/snapshot_boxplot.png")) - assert snapshot._image.tobytes() == current._image.tobytes() + + # Inlining the expression into the assert causes pytest to hang if the assertion fails when run from PyCharm. + assertion = snapshot._image.tobytes() == current._image.tobytes() + assert assertion def test_should_raise_if_column_contains_non_numerical_values() -> None: diff --git a/tests/safeds/data/tabular/containers/_column/test_plot_histogram.py b/tests/safeds/data/tabular/containers/_column/test_plot_histogram.py index 0ba8e1c33..fe4157b6a 100644 --- a/tests/safeds/data/tabular/containers/_column/test_plot_histogram.py +++ b/tests/safeds/data/tabular/containers/_column/test_plot_histogram.py @@ -8,7 +8,10 @@ def test_should_match_snapshot_numeric() -> None: table = Table({"A": [1, 2, 3]}) current = table.get_column("A").plot_histogram() snapshot = Image.from_png_file(resolve_resource_path("./image/snapshot_histogram_numeric.png")) - assert snapshot._image.tobytes() == current._image.tobytes() + + # Inlining the expression into the assert causes pytest to hang if the assertion fails when run from PyCharm. + assertion = snapshot._image.tobytes() == current._image.tobytes() + assert assertion def test_should_match_snapshot_str() -> None: diff --git a/tests/safeds/data/tabular/containers/_table/test_plot_correlation_heatmap.py b/tests/safeds/data/tabular/containers/_table/test_plot_correlation_heatmap.py index 6fa0e1da2..5ba397c68 100644 --- a/tests/safeds/data/tabular/containers/_table/test_plot_correlation_heatmap.py +++ b/tests/safeds/data/tabular/containers/_table/test_plot_correlation_heatmap.py @@ -7,5 +7,8 @@ def test_should_match_snapshot() -> None: table = Table({"A": [1, 2, 3.5], "B": [0.2, 4, 77]}) current = table.plot_correlation_heatmap() - legacy = Image.from_png_file(resolve_resource_path("./image/snapshot_heatmap.png")) - assert legacy._image.tobytes() == current._image.tobytes() + snapshot = Image.from_png_file(resolve_resource_path("./image/snapshot_heatmap.png")) + + # Inlining the expression into the assert causes pytest to hang if the assertion fails when run from PyCharm. + assertion = snapshot._image.tobytes() == current._image.tobytes() + assert assertion diff --git a/tests/safeds/data/tabular/containers/_table/test_plot_histograms.py b/tests/safeds/data/tabular/containers/_table/test_plot_histograms.py new file mode 100644 index 000000000..16572de52 --- /dev/null +++ b/tests/safeds/data/tabular/containers/_table/test_plot_histograms.py @@ -0,0 +1,24 @@ +import pytest +from safeds.data.image.containers import Image +from safeds.data.tabular.containers import Table + +from tests.helpers import resolve_resource_path + + +@pytest.mark.parametrize( + ("table", "path"), + [ + (Table({"A": [1, 2, 3]}), "./image/snapshot_histograms/one_column.png"), + ( + Table({"A": [1, 2, 3], "B": ["A", "A", "Bla"], "C": [True, True, False], "D": [1.0, 2.1, 4.5]}), + "./image/snapshot_histograms/four_columns.png", + ), + ], +) +def test_should_match_snapshot(table: Table, path: str) -> None: + current = table.plot_histograms() + snapshot = Image.from_png_file(resolve_resource_path(path)) + + # Inlining the expression into the assert causes pytest to hang if the assertion fails when run from PyCharm. + assertion = snapshot._image.tobytes() == current._image.tobytes() + assert assertion diff --git a/tests/safeds/data/tabular/containers/_table/test_plot_lineplot.py b/tests/safeds/data/tabular/containers/_table/test_plot_lineplot.py index a370c3146..480d5b147 100644 --- a/tests/safeds/data/tabular/containers/_table/test_plot_lineplot.py +++ b/tests/safeds/data/tabular/containers/_table/test_plot_lineplot.py @@ -10,7 +10,10 @@ def test_should_match_snapshot() -> None: table = Table({"A": [1, 2, 3], "B": [2, 4, 7]}) current = table.plot_lineplot("A", "B") snapshot = Image.from_png_file(resolve_resource_path("./image/snapshot_lineplot.png")) - assert snapshot._image.tobytes() == current._image.tobytes() + + # Inlining the expression into the assert causes pytest to hang if the assertion fails when run from PyCharm. + assertion = snapshot._image.tobytes() == current._image.tobytes() + assert assertion @pytest.mark.parametrize( diff --git a/tests/safeds/data/tabular/containers/_table/test_plot_scatterplot.py b/tests/safeds/data/tabular/containers/_table/test_plot_scatterplot.py index 0ff0ed090..f23353517 100644 --- a/tests/safeds/data/tabular/containers/_table/test_plot_scatterplot.py +++ b/tests/safeds/data/tabular/containers/_table/test_plot_scatterplot.py @@ -10,7 +10,10 @@ def test_should_match_snapshot() -> None: table = Table({"A": [1, 2, 3], "B": [2, 4, 7]}) current = table.plot_scatterplot("A", "B") snapshot = Image.from_png_file(resolve_resource_path("./image/snapshot_scatterplot.png")) - assert snapshot._image.tobytes() == current._image.tobytes() + + # Inlining the expression into the assert causes pytest to hang if the assertion fails when run from PyCharm. + assertion = snapshot._image.tobytes() == current._image.tobytes() + assert assertion @pytest.mark.parametrize(