diff --git a/examples/histogram_binning_options.py b/examples/histogram_binning_options.py new file mode 100644 index 0000000000..de33c7d690 --- /dev/null +++ b/examples/histogram_binning_options.py @@ -0,0 +1,50 @@ +""" +Histogram binning options on data that does not fit the bin size. +================================================================= + +""" +import pandas as pd + +import seaborn as sns +import matplotlib.pyplot as plt + +if __name__ == '__main__': + df_round_up = pd.DataFrame({"x": list(range(58))}) + df_round_down = pd.DataFrame({"x": list(range(52))}) + n_plots = 4 + + fig, ax = plt.subplots(n_plots, 2, figsize=(5 * 2, 5 * n_plots)) + fig.suptitle("Histogram binning options\nrange(58) and range(52)") + fig.tight_layout() + + hist_n_bins_ru = sns.histplot(df_round_up, + x="x", bins=10, ax=ax[0][0]) + hist_n_bins_ru.set_title("bins=10") + hist_n_bins_rd = sns.histplot(df_round_down, + x="x", bins=10, ax=ax[0][1]) + hist_n_bins_rd.set_title("bins=10") + + hist_binwidth_ru = sns.histplot(df_round_up, + x="x", binwidth=10, ax=ax[1][0]) + hist_binwidth_ru.set_title("binwidth=10") + hist_binwidth_rd = sns.histplot(df_round_down, + x="x", binwidth=10, ax=ax[1][1]) + hist_binwidth_rd.set_title("binwidth=10") + + hist_binrange_ru = sns.histplot(df_round_up, + x="x", binrange=(0, 60), ax=ax[2][0]) + hist_binrange_ru.set_title("binrange=(0, 60)") + hist_binrange_rd = sns.histplot(df_round_down, + x="x", binrange=(0, 60), ax=ax[2][1]) + hist_binrange_rd.set_title("binrange=(0, 60)") + + hist_binrange_and_width_ru = sns.histplot(df_round_up, + x="x", binwidth=10, + binrange=(0, 60), ax=ax[3][0]) + hist_binrange_and_width_ru.set_title("bw=10, br=(0, 60)") + hist_binrange_and_width_rd = sns.histplot(df_round_down, + x="x", binwidth=10, + binrange=(0, 60), ax=ax[3][1]) + hist_binrange_and_width_rd.set_title("bw=10, br=(0, 60)") + + plt.savefig("output_files/binning_options.svg") diff --git a/seaborn/_stats/counting.py b/seaborn/_stats/counting.py index 0c2fb7d499..536a4c0f16 100644 --- a/seaborn/_stats/counting.py +++ b/seaborn/_stats/counting.py @@ -133,7 +133,7 @@ def _define_bin_edges(self, vals, weight, bins, binwidth, binrange, discrete): bin_edges = np.arange(start - .5, stop + 1.5) else: if binwidth is not None: - bins = int(round((stop - start) / binwidth)) + bins = max(int(round((stop - start) / binwidth)), 1) bin_edges = np.histogram_bin_edges(vals, bins, binrange, weight) # TODO warning or cap on too many bins? diff --git a/tests/conftest.py b/tests/conftest.py index 6ee53e7ee4..fe279fe7fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -136,6 +136,18 @@ def long_df(rng): return df +@pytest.fixture +def mini_bin_df(rng): + n = 100 + df1 = pd.DataFrame({"x": rng.uniform(0, 100, n // 2), + "a": "a", + "b": "x"}) + df2 = pd.DataFrame({"x": rng.uniform(0, 1, n // 2), + "a": "b", + "b": "y"}) + return pd.concat([df1, df2]) + + @pytest.fixture def long_dict(long_df): diff --git a/tests/test_axisgrid.py b/tests/test_axisgrid.py index 6470edfa4f..ee8777b6a7 100644 --- a/tests/test_axisgrid.py +++ b/tests/test_axisgrid.py @@ -721,6 +721,10 @@ def test_data_interchange(self, mock_long_df, long_df): for ax in g.axes.flat: assert len(ax.collections) == 1 + def test_histplot_with_mini_bin(self, mini_bin_df): + grid = ag.FacetGrid(mini_bin_df, col='b', col_wrap=2, hue="a") + grid.map(histplot, "x", binwidth=10) + class TestPairGrid: