Repository: arviz-devs/arviz

Issue:
Histogram distribution plot fails on hist, succeeds on KDE
**Describe the bug**
I was plotting posterior data from an InferenceData object, and the KDE worked fine.  But I was interested to see more exactly what the samples looked like, so I tried plotting with a histogram instead.  This errored out, with the backtrace below.

To the extent I can tell, it looks like Arviz grabbed up a multi-dimensional array (`chains` x `samples` x `inputs`, 4 x 500 x 4) and instead of flattening it -- as the KDE seems to have done -- or splitting it out into four plots for the `inputs` dimension), it errored out.

**To Reproduce**
Steps to reproduce the behavior. Ideally a self-contained snippet of code, or link to a notebook or external code. Please include screenshots/images produced with ArviZ here, or the stacktrace including `arviz` code to help.

```
import arviz as az
idata = az.from_netcdf('/Users/rpg/Dropbox/arviz-hist-bug.nc')  
az.plot_dist(idata.posterior['err_sd'])  # works fine
az.plot_dist(idata.posterior['err_sd'], kind='hist')   # error in np.reshape()
```

**Expected behavior**
I expected to get a histogram plot, or possibly four.

Alternatively, if I was using this wrong, I would expect to get an error for the KDE case as well as the basis case.

**Additional context**

* Arviz version was today's `master`.
* NetCDF file to reproduce the error is here: https://www.dropbox.com/s/181nuxivbkqr8ck/arviz-hist-bug.nc?dl=0

# Backtrace

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-10-c027c32088dc> in <module>
----> 1 az.plot_dist(idata.posterior['err_sd'], kind='hist')

~/src/arviz/arviz/plots/distplot.py in plot_dist(values, values2, color, kind, cumulative, label, rotated, rug, bw, quantiles, contour, fill_last, textsize, plot_kwargs, fill_kwargs, rug_kwargs, contour_kwargs, contourf_kwargs, pcolormesh_kwargs, hist_kwargs, ax, backend, backend_kwargs, show, **kwargs)
    197 
    198     plot = get_plotting_function("plot_dist", "distplot", backend)
--> 199     ax = plot(**dist_plot_args)
    200     return ax

~/src/arviz/arviz/plots/backends/matplotlib/distplot.py in plot_dist(values, values2, color, kind, cumulative, label, rotated, rug, bw, quantiles, contour, fill_last, textsize, plot_kwargs, fill_kwargs, rug_kwargs, contour_kwargs, contourf_kwargs, pcolormesh_kwargs, hist_kwargs, ax, backend_kwargs, show)
     47     if kind == "hist":
     48         ax = _histplot_mpl_op(
---> 49             values=values, values2=values2, rotated=rotated, ax=ax, hist_kwargs=hist_kwargs
     50         )
     51 

~/src/arviz/arviz/plots/backends/matplotlib/distplot.py in _histplot_mpl_op(values, values2, rotated, ax, hist_kwargs)
     93     bins = hist_kwargs.pop("bins")
     94 
---> 95     ax.hist(values, bins=bins, **hist_kwargs)
     96     if rotated:
     97         ax.set_yticks(bins[:-1])

~/.virtualenvs/xplan-dev-env/lib/python3.6/site-packages/matplotlib/__init__.py in inner(ax, data, *args, **kwargs)
   1599     def inner(ax, *args, data=None, **kwargs):
   1600         if data is None:
-> 1601             return func(ax, *map(sanitize_sequence, args), **kwargs)
   1602 
   1603         bound = new_sig.bind(ax, *args, **kwargs)

~/.virtualenvs/xplan-dev-env/lib/python3.6/site-packages/matplotlib/axes/_axes.py in hist(self, x, bins, range, density, weights, cumulative, bottom, histtype, align, orientation, rwidth, log, color, label, stacked, normed, **kwargs)
   6686         input_empty = np.size(x) == 0
   6687         # Massage 'x' for processing.
-> 6688         x = cbook._reshape_2D(x, 'x')
   6689         nx = len(x)  # number of datasets
   6690 

~/.virtualenvs/xplan-dev-env/lib/python3.6/site-packages/matplotlib/cbook/__init__.py in _reshape_2D(X, name)
   1428         return [np.reshape(x, -1) for x in X]
   1429     else:
-> 1430         raise ValueError("{} must have 2 or fewer dimensions".format(name))
   1431 
   1432 

ValueError: x must have 2 or fewer dimensions
```


Discussion:

Patch:
diff --git a/.azure-pipelines/azure-pipelines-base.yml b/.azure-pipelines/azure-pipelines-base.yml
index 9f2313a..6bd57f8 100644
--- a/.azure-pipelines/azure-pipelines-base.yml
+++ b/.azure-pipelines/azure-pipelines-base.yml
@@ -69,5 +69,13 @@ jobs:
     displayName: 'Publish Test Results'
 
   - script: |
-      python -m codecov --token=$(CODECOV_TOKEN)
+      for i in {1..5}
+      do
+          python -m codecov --token $(CODECOV_TOKEN) --name "$(NAME)" --required -v
+          if [ "$?" -eq 0 ]
+          then
+              break
+          fi
+          sleep 90
+      done
     displayName: 'upload coverage'
diff --git a/.azure-pipelines/azure-pipelines-docs.yml b/.azure-pipelines/azure-pipelines-docs.yml
index ea157e9..0fa23fc 100644
--- a/.azure-pipelines/azure-pipelines-docs.yml
+++ b/.azure-pipelines/azure-pipelines-docs.yml
@@ -42,7 +42,7 @@ jobs:
       python -m pip install --no-cache-dir -r requirements-dev.txt
       python -m pip install --no-cache-dir -r requirements-optional.txt
       python -m pip install --no-cache-dir -r requirements-docs.txt
-      conda install --channel conda-forge -y phantomjs
+      conda install -y geckodriver firefox --channel conda-forge
     displayName: 'Install requirements'
 
   - script: |
diff --git a/.azure-pipelines/azure-pipelines-external.yml b/.azure-pipelines/azure-pipelines-external.yml
index aafcf6b..3c7d0d5 100644
--- a/.azure-pipelines/azure-pipelines-external.yml
+++ b/.azure-pipelines/azure-pipelines-external.yml
@@ -128,5 +128,13 @@ jobs:
       testRunTitle: 'Publish test results for Python $(python.version)'
 
   - script: |
-      python -m codecov --token=$(CODECOV_TOKEN)
+      for i in {1..5}
+      do
+          python -m codecov --token $(CODECOV_TOKEN) --name "$(NAME)" --required -v
+          if [ "$?" -eq 0 ]
+          then
+              break
+          fi
+          sleep 90
+      done
     displayName: 'upload coverage'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1423f17..e6cad85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,10 +5,12 @@
 ### New features
 * Integrate jointplot into pairplot, add point-estimate and overlay of plot kinds #1079
 ### Maintenance and fixes
-
+* Fixed behaviour of `credible_interval=None` in `plot_posterior` (#1115)
+* Fixed hist kind of `plot_dist` with multidimensional input (#1115)
 ### Deprecation
 
 ### Documentation
+* Image thumbs generation updated to be Bokeh 2 compatible (#1116)
 
 
 ## v0.7.0 (2020 Mar 2)
diff --git a/arviz/plots/backends/bokeh/distplot.py b/arviz/plots/backends/bokeh/distplot.py
index 42f8917..987c616 100644
--- a/arviz/plots/backends/bokeh/distplot.py
+++ b/arviz/plots/backends/bokeh/distplot.py
@@ -115,7 +115,7 @@ def _histplot_bokeh_op(values, values2, rotated, ax, hist_kwargs):
     if bins is None:
         bins = get_bins(values)
     density = hist_kwargs.pop("density", True)
-    hist, edges = np.histogram(values, density=density, bins=bins)
+    hist, edges = np.histogram(np.asarray(values).flatten(), density=density, bins=bins)
     if hist_kwargs.pop("cumulative", False):
         hist = np.cumsum(hist)
         hist /= hist[-1]
diff --git a/arviz/plots/backends/matplotlib/distplot.py b/arviz/plots/backends/matplotlib/distplot.py
index 1c56e27..193bcc9 100644
--- a/arviz/plots/backends/matplotlib/distplot.py
+++ b/arviz/plots/backends/matplotlib/distplot.py
@@ -1,10 +1,11 @@
 """Matplotlib distplot."""
 import warnings
 import matplotlib.pyplot as plt
-
+import numpy as np
 from . import backend_show
 from ...kdeplot import plot_kde
 from ...plot_utils import matplotlib_kwarg_dealiaser
+from ...plot_utils import get_bins
 
 
 def plot_dist(
@@ -91,8 +92,10 @@ def _histplot_mpl_op(values, values2, rotated, ax, hist_kwargs):
         raise NotImplementedError("Insert hexbin plot here")
 
     bins = hist_kwargs.pop("bins")
+    if bins is None:
+        bins = get_bins(values)
+    ax.hist(np.asarray(values).flatten(), bins=bins, **hist_kwargs)
 
-    ax.hist(values, bins=bins, **hist_kwargs)
     if rotated:
         ax.set_yticks(bins[:-1])
     else:
diff --git a/arviz/plots/distplot.py b/arviz/plots/distplot.py
index cb2234d..af7aefc 100644
--- a/arviz/plots/distplot.py
+++ b/arviz/plots/distplot.py
@@ -1,7 +1,9 @@
 # pylint: disable=unexpected-keyword-arg
 """Plot distribution as histogram or kernel density estimates."""
 
+import xarray as xr
 from .plot_utils import get_bins, get_plotting_function, matplotlib_kwarg_dealiaser
+from ..data import InferenceData
 from ..rcparams import rcParams
 
 
@@ -142,6 +144,13 @@ def plot_dist(
 
         >>> az.plot_dist(b, rug=True, quantiles=[.25, .5, .75], cumulative=True)
     """
+    if isinstance(values, (InferenceData, xr.Dataset)):
+        raise ValueError(
+            "InferenceData or xarray.Dateset object detected,"
+            " use plot_posterior, plot_density or plot_pair"
+            " instead of plot_dist"
+        )
+
     if kind not in ["auto", "kde", "hist"]:
         raise TypeError('Invalid "kind":{}. Select from {{"auto","kde","hist"}}'.format(kind))
 
diff --git a/arviz/plots/posteriorplot.py b/arviz/plots/posteriorplot.py
index 0d1139f..e7656e9 100644
--- a/arviz/plots/posteriorplot.py
+++ b/arviz/plots/posteriorplot.py
@@ -22,7 +22,7 @@ def plot_posterior(
     coords=None,
     figsize=None,
     textsize=None,
-    credible_interval=None,
+    credible_interval="auto",
     multimodal=False,
     round_to: Optional[int] = None,
     point_estimate="auto",
@@ -189,9 +189,9 @@ def plot_posterior(
     if coords is None:
         coords = {}
 
-    if credible_interval is None:
+    if credible_interval == "auto":
         credible_interval = rcParams["stats.credible_interval"]
-    else:
+    elif credible_interval is not None:
         if not 1 >= credible_interval > 0:
             raise ValueError("The value of credible_interval should be in the interval (0, 1]")
 
diff --git a/arviz/stats/stats_utils.py b/arviz/stats/stats_utils.py
index 610fd81..e26af7b 100644
--- a/arviz/stats/stats_utils.py
+++ b/arviz/stats/stats_utils.py
@@ -142,7 +142,8 @@ def make_ufunc(
             if out_shape is None:
                 out = tuple(np.empty(element_shape) for _ in range(n_output))
             else:
-                out = tuple(np.empty((*element_shape, *out_shape)) for _ in range(n_output))
+                out = tuple(np.empty((*element_shape, *out_shape[i])) for i in range(n_output))
+
         elif check_shape:
             raise_error = False
             correct_shape = tuple(element_shape for _ in range(n_output))
@@ -193,6 +194,7 @@ def wrap_xarray_ufunc(
         Arguments passed to 'ufunc'.
     func_kwargs : dict
         Keyword arguments passed to 'ufunc'.
+            - 'out_shape', int, by default None
     **kwargs
         Passed to xarray.apply_ufunc.
 
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 03fd388..2f99536 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -11,6 +11,11 @@ trigger:
     include:
     - '*'
 
+pr:
+  branches:
+    include:
+    - master
+
 jobs:
   - template: .azure-pipelines/azure-pipelines-base.yml
   - template: .azure-pipelines/azure-pipelines-external.yml
diff --git a/doc/sphinxext/gallery_generator.py b/doc/sphinxext/gallery_generator.py
index 06df4a2..1b3bc36 100644
--- a/doc/sphinxext/gallery_generator.py
+++ b/doc/sphinxext/gallery_generator.py
@@ -36,7 +36,7 @@ MPL_RST_TEMPLATE = """
 
 **Python source code:** :download:`[download source: {fname}]<{fname}>`
 
-**API documentation:** {api_name} 
+**API documentation:** {api_name}
 
 .. literalinclude:: {fname}
     :lines: {end_line}-
@@ -52,7 +52,7 @@ BOKEH_RST_TEMPLATE = """
 
 **Python source code:** :download:`[download source: {fname}]<{fname}>`
 
-**API documentation:** {api_name} 
+**API documentation:** {api_name}
 
 .. literalinclude:: {fname}
     :lines: {end_line}-
@@ -63,11 +63,11 @@ RST_TEMPLATES = {"matplotlib": MPL_RST_TEMPLATE, "bokeh": BOKEH_RST_TEMPLATE}
 BOKEH_EXPORT_CODE = """\n
 if isinstance(ax, ndarray):
     if len(ax.shape) == 1:
-        export_png(gridplot([ax.tolist()]), "{pngfilename}")
+        export_png(gridplot([ax.tolist()]), filename="{pngfilename}")
     else:
-        export_png(gridplot(ax.tolist()), "{pngfilename}")
+        export_png(gridplot(ax.tolist()), filename="{pngfilename}")
 else:
-    export_png(ax, "{pngfilename}")
+    export_png(ax, filename="{pngfilename}")
 """
 
 INDEX_TEMPLATE = """


Test patch:
diff --git a/arviz/tests/base_tests/test_stats_utils.py b/arviz/tests/base_tests/test_stats_utils.py
index d1a7827..0d68846 100644
--- a/arviz/tests/base_tests/test_stats_utils.py
+++ b/arviz/tests/base_tests/test_stats_utils.py
@@ -104,6 +104,54 @@ def test_wrap_ufunc_output(quantile, arg):
         assert len(res) == n_output
 
 
+@pytest.mark.parametrize("out_shape", ((1, 2), (1, 2, 3), (2, 3, 4, 5)))
+@pytest.mark.parametrize("input_dim", ((4, 100), (4, 100, 3), (4, 100, 4, 5)))
+def test_wrap_ufunc_out_shape(out_shape, input_dim):
+    func = lambda x: np.random.rand(*out_shape)
+    ary = np.ones(input_dim)
+    res = wrap_xarray_ufunc(
+        func, ary, func_kwargs={"out_shape": out_shape}, ufunc_kwargs={"n_dims": 1}
+    )
+    assert res.shape == (*ary.shape[:-1], *out_shape)
+
+
+def test_wrap_ufunc_out_shape_multi_input():
+    out_shape = (2, 4)
+    func = lambda x, y: np.random.rand(*out_shape)
+    ary1 = np.ones((4, 100))
+    ary2 = np.ones((4, 5))
+    res = wrap_xarray_ufunc(
+        func, ary1, ary2, func_kwargs={"out_shape": out_shape}, ufunc_kwargs={"n_dims": 1}
+    )
+    assert res.shape == (*ary1.shape[:-1], *out_shape)
+
+
+def test_wrap_ufunc_out_shape_multi_output_same():
+    func = lambda x: (np.random.rand(1, 2), np.random.rand(1, 2))
+    ary = np.ones((4, 100))
+    res1, res2 = wrap_xarray_ufunc(
+        func,
+        ary,
+        func_kwargs={"out_shape": ((1, 2), (1, 2))},
+        ufunc_kwargs={"n_dims": 1, "n_output": 2},
+    )
+    assert res1.shape == (*ary.shape[:-1], 1, 2)
+    assert res2.shape == (*ary.shape[:-1], 1, 2)
+
+
+def test_wrap_ufunc_out_shape_multi_output_diff():
+    func = lambda x: (np.random.rand(5, 3), np.random.rand(10, 4))
+    ary = np.ones((4, 100))
+    res1, res2 = wrap_xarray_ufunc(
+        func,
+        ary,
+        func_kwargs={"out_shape": ((5, 3), (10, 4))},
+        ufunc_kwargs={"n_dims": 1, "n_output": 2},
+    )
+    assert res1.shape == (*ary.shape[:-1], 5, 3)
+    assert res2.shape == (*ary.shape[:-1], 10, 4)
+
+
 @pytest.mark.parametrize("n_output", (1, 2, 3))
 def test_make_ufunc(n_output):
     if n_output == 3: