Repository: arviz-devs/arviz

Issue:
Docker image is missing some important packages
Hi all,

I'm trying to open a Jupyter notebook in the Docker image as per `CONTRIBUTING.md`, and when running from a (relatively new) Docker image I am finding that I cannot run `import arviz as az` without finding that we are missing the following packages:
- `scipy`
- `xarray`
- `matplotlib`
- `netCDF4`

Once I installed these for packages, I was finally able to import the package.

**To Reproduce**
- Download docker image.
- Open Docker Jupyter notebook.
- Try to import `arviz` package.
- For whatever module it complains its missing, go back, pip install in the Docker image, and then restart.
- Repeat this and you get the 4 missing packages above.

**Expected behavior**
I should be able to import the package without any trouble from a clean Docker image.

**Additional context**
MacOSX 10.14 computer, Docker image downloaded on 2019-12-03 at around 3 PM PST.


Discussion:
OriolAbril: LGTM

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6b397bd..a4e8c9b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -203,8 +203,8 @@ To start a bash shell inside Docker, run:
 Alternatively, to start a jupyter notebook, there are two steps, first run:
 
     $ docker run --mount type=bind,source="$(pwd)",target=/opt/arviz/ --name jupyter-dock -it -d -p 8888:8888 arviz
-    $ docker exec -it jupyter-dock pip install jupyter
-    $ docker exec -it jupyter-dock jupyter notebook --ip 0.0.0.0 --no-browser --allow-root
+    $ docker exec jupyter-dock bash -c "pip install jupyter"
+    $ docker exec -it jupyter-dock bash -c "jupyter notebook --ip 0.0.0.0 --no-browser --allow-root"
 
 This will output something similar to `http://(<docker container id> or <ip>):8888/?token=<token id>`, and can be accessed at `http://localhost:8888/?token=<token id>`.
 
diff --git a/arviz/data/inference_data.py b/arviz/data/inference_data.py
index cbfa34f..6a5985e 100644
--- a/arviz/data/inference_data.py
+++ b/arviz/data/inference_data.py
@@ -304,8 +304,8 @@ def concat(*args, dim=None, copy=True, inplace=False, reset_dim=True):
                     )
                     msg += " Valid dimensions are `chain` and `draw`."
                     raise TypeError(msg)
-            group_data = getattr(arg, group)
-            args_groups[group] = deepcopy(group_data) if copy else group_data
+                group_data = getattr(arg, group)
+                args_groups[group] = deepcopy(group_data) if copy else group_data
         # add arg0 to args_groups if inplace is False
         if not inplace:
             for group in arg0_groups:
diff --git a/arviz/plots/backends/bokeh/bokeh_violinplot.py b/arviz/plots/backends/bokeh/bokeh_violinplot.py
new file mode 100644
index 0000000..f77232a
--- /dev/null
+++ b/arviz/plots/backends/bokeh/bokeh_violinplot.py
@@ -0,0 +1,101 @@
+"""Bokeh Violinplot."""
+import bokeh.plotting as bkp
+from bokeh.layouts import gridplot
+from bokeh.models.annotations import Title
+import numpy as np
+
+from ....stats import hpd
+from ....stats.stats_utils import histogram
+from ...kdeplot import _fast_kde
+from ...plot_utils import get_bins, make_label, _create_axes_grid
+
+
+def _plot_violin(
+    ax,
+    plotters,
+    figsize,
+    rows,
+    cols,
+    sharey,
+    kwargs_shade,
+    shade,
+    bw,
+    credible_interval,
+    linewidth,
+    quartiles,
+    show,
+):
+    if ax is None:
+        _, ax = _create_axes_grid(
+            len(plotters),
+            rows,
+            cols,
+            sharey=sharey,
+            figsize=figsize,
+            squeeze=False,
+            backend="bokeh",
+        )
+
+    ax = np.atleast_1d(ax)
+
+    for (var_name, selection, x), ax_ in zip(plotters, ax.flatten()):
+        val = x.flatten()
+        if val[0].dtype.kind == "i":
+            cat_hist(val, shade, ax_, **kwargs_shade)
+        else:
+            _violinplot(val, shade, bw, ax_, **kwargs_shade)
+
+        per = np.percentile(val, [25, 75, 50])
+        hpd_intervals = hpd(val, credible_interval, multimodal=False)
+
+        if quartiles:
+            ax_.line([0, 0], per[:2], line_width=linewidth * 3, line_color="black")
+        ax_.line([0, 0], hpd_intervals, line_width=linewidth, line_color="black")
+        ax_.circle(0, per[-1])
+
+        _title = Title()
+        _title.text = make_label(var_name, selection)
+        ax_.title = _title
+        ax_.xaxis.major_tick_line_color = None
+        ax_.xaxis.minor_tick_line_color = None
+        ax_.xaxis.major_label_text_font_size = "0pt"
+
+    if show:
+        grid = gridplot([list(item) for item in ax], toolbar_location="above")
+        bkp.show(grid)
+
+    return ax
+
+
+def _violinplot(val, shade, bw, ax, **kwargs_shade):
+    """Auxiliary function to plot violinplots."""
+    density, low_b, up_b = _fast_kde(val, bw=bw)
+    x = np.linspace(low_b, up_b, len(density))
+
+    x = np.concatenate([x, x[::-1]])
+    density = np.concatenate([-density, density[::-1]])
+
+    ax.patch(density, x, fill_alpha=shade, line_width=0, **kwargs_shade)
+
+
+def cat_hist(val, shade, ax, **kwargs_shade):
+    """Auxiliary function to plot discrete-violinplots."""
+    bins = get_bins(val)
+    _, binned_d, _ = histogram(val, bins=bins)
+
+    bin_edges = np.linspace(np.min(val), np.max(val), len(bins))
+    centers = 0.5 * (bin_edges + np.roll(bin_edges, 1))[:-1]
+    heights = np.diff(bin_edges)
+
+    lefts = -0.5 * binned_d
+
+    ax.hbar(
+        y=centers,
+        left=lefts,
+        right=-lefts,
+        height=heights,
+        fill_alpha=shade,
+        line_alpha=shade,
+        line_color=None,
+        **kwargs_shade
+    )
diff --git a/arviz/plots/backends/matplotlib/mpl_violinplot.py b/arviz/plots/backends/matplotlib/mpl_violinplot.py
new file mode 100644
index 0000000..725149d
--- /dev/null
+++ b/arviz/plots/backends/matplotlib/mpl_violinplot.py
@@ -0,0 +1,77 @@
+"""Matplotlib Violinplot."""
+import numpy as np
+
+from ....stats import hpd
+from ....stats.stats_utils import histogram
+from ...kdeplot import _fast_kde
+from ...plot_utils import get_bins, make_label, _create_axes_grid
+
+
+def _plot_violin(
+    ax,
+    plotters,
+    figsize,
+    rows,
+    cols,
+    sharey,
+    kwargs_shade,
+    shade,
+    bw,
+    credible_interval,
+    linewidth,
+    ax_labelsize,
+    xt_labelsize,
+    quartiles,
+):
+    if ax is None:
+        _, ax = _create_axes_grid(
+            len(plotters), rows, cols, sharey=sharey, figsize=figsize, squeeze=False
+        )
+
+    ax = np.atleast_1d(ax)
+
+    for (var_name, selection, x), ax_ in zip(plotters, ax.flatten()):
+        val = x.flatten()
+        if val[0].dtype.kind == "i":
+            cat_hist(val, shade, ax_, **kwargs_shade)
+        else:
+            _violinplot(val, shade, bw, ax_, **kwargs_shade)
+
+        per = np.percentile(val, [25, 75, 50])
+        hpd_intervals = hpd(val, credible_interval, multimodal=False)
+
+        if quartiles:
+            ax_.plot([0, 0], per[:2], lw=linewidth * 3, color="k", solid_capstyle="round")
+        ax_.plot([0, 0], hpd_intervals, lw=linewidth, color="k", solid_capstyle="round")
+        ax_.plot(0, per[-1], "wo", ms=linewidth * 1.5)
+
+        ax_.set_xlabel(make_label(var_name, selection), fontsize=ax_labelsize)
+        ax_.set_xticks([])
+        ax_.tick_params(labelsize=xt_labelsize)
+        ax_.grid(None, axis="x")
+
+    return ax
+
+
+def _violinplot(val, shade, bw, ax, **kwargs_shade):
+    """Auxiliary function to plot violinplots."""
+    density, low_b, up_b = _fast_kde(val, bw=bw)
+    x = np.linspace(low_b, up_b, len(density))
+
+    x = np.concatenate([x, x[::-1]])
+    density = np.concatenate([-density, density[::-1]])
+
+    ax.fill_betweenx(x, density, alpha=shade, lw=0, **kwargs_shade)
+
+
+def cat_hist(val, shade, ax, **kwargs_shade):
+    """Auxiliary function to plot discrete-violinplots."""
+    bins = get_bins(val)
+    _, binned_d, _ = histogram(val, bins=bins)
+
+    bin_edges = np.linspace(np.min(val), np.max(val), len(bins))
+    centers = 0.5 * (bin_edges + np.roll(bin_edges, 1))[:-1]
+    heights = np.diff(bin_edges)
+
+    lefts = -0.5 * binned_d
+    ax.barh(centers, binned_d, height=heights, left=lefts, alpha=shade, **kwargs_shade)
diff --git a/arviz/plots/violinplot.py b/arviz/plots/violinplot.py
index bfc1686..b8e621b 100644
--- a/arviz/plots/violinplot.py
+++ b/arviz/plots/violinplot.py
@@ -1,12 +1,6 @@
 """Plot posterior traces as violin plot."""
-import matplotlib.pyplot as plt
-import numpy as np
-
 from ..data import convert_to_dataset
-from ..stats import hpd
-from ..stats.stats_utils import histogram
-from .kdeplot import _fast_kde
-from .plot_utils import get_bins, _scale_fig_size, xarray_var_iter, make_label, filter_plotters_list
+from .plot_utils import _scale_fig_size, xarray_var_iter, filter_plotters_list, default_grid
 from ..utils import _var_names
 
 
@@ -22,6 +16,8 @@ def plot_violin(
     textsize=None,
     ax=None,
     kwargs_shade=None,
+    backend=None,
+    show=True,
 ):
     """Plot posterior of traces as violin plot.
 
@@ -73,68 +69,40 @@ def plot_violin(
     if kwargs_shade is None:
         kwargs_shade = {}
 
+    rows, cols = default_grid(len(plotters))
+
     (figsize, ax_labelsize, _, xt_labelsize, linewidth, _) = _scale_fig_size(
-        figsize, textsize, 1, len(plotters)
+        figsize, textsize, rows, cols
     )
     ax_labelsize *= 2
 
-    if ax is None:
-        fig, ax = plt.subplots(
-            1, len(plotters), figsize=figsize, sharey=sharey, constrained_layout=True
-        )
-
-    else:
-        fig = ax.figure
-
-    ax = np.atleast_1d(ax)
-
-    for axind, (var_name, selection, x) in enumerate(plotters):
-        val = x.flatten()
-        if val[0].dtype.kind == "i":
-            cat_hist(val, shade, ax[axind], **kwargs_shade)
-        else:
-            _violinplot(val, shade, bw, ax[axind], **kwargs_shade)
-
-        per = np.percentile(val, [25, 75, 50])
-        hpd_intervals = hpd(val, credible_interval, multimodal=False)
-
-        if quartiles:
-            ax[axind].plot([0, 0], per[:2], lw=linewidth * 3, color="k", solid_capstyle="round")
-        ax[axind].plot([0, 0], hpd_intervals, lw=linewidth, color="k", solid_capstyle="round")
-        ax[axind].plot(0, per[-1], "wo", ms=linewidth * 1.5)
+    violinplot_kwargs = dict(
+        ax=ax,
+        plotters=plotters,
+        figsize=figsize,
+        rows=rows,
+        cols=cols,
+        sharey=sharey,
+        kwargs_shade=kwargs_shade,
+        shade=shade,
+        bw=bw,
+        credible_interval=credible_interval,
+        linewidth=linewidth,
+        ax_labelsize=ax_labelsize,
+        xt_labelsize=xt_labelsize,
+        quartiles=quartiles,
+    )
 
-        ax[axind].set_xlabel(make_label(var_name, selection), fontsize=ax_labelsize)
-        ax[axind].set_xticks([])
-        ax[axind].tick_params(labelsize=xt_labelsize)
-        ax[axind].grid(None, axis="x")
+    if backend == "bokeh":
+        from .backends.bokeh.bokeh_violinplot import _plot_violin
 
-    if sharey:
-        fig.subplots_adjust(wspace=0)
+        violinplot_kwargs.pop("ax_labelsize")
+        violinplot_kwargs.pop("xt_labelsize")
+        violinplot_kwargs["show"] = show
+        ax = _plot_violin(**violinplot_kwargs)  #  pylint: disable=unexpected-keyword-arg
     else:
-        fig.tight_layout()
+        from .backends.matplotlib.mpl_violinplot import _plot_violin
 
-    return ax
-
-
-def _violinplot(val, shade, bw, ax, **kwargs_shade):
-    """Auxiliary function to plot violinplots."""
-    density, low_b, up_b = _fast_kde(val, bw=bw)
-    x = np.linspace(low_b, up_b, len(density))
-
-    x = np.concatenate([x, x[::-1]])
-    density = np.concatenate([-density, density[::-1]])
-
-    ax.fill_betweenx(x, density, alpha=shade, lw=0, **kwargs_shade)
+        ax = _plot_violin(**violinplot_kwargs)
 
-
-def cat_hist(val, shade, ax, **kwargs_shade):
-    """Auxiliary function to plot discrete-violinplots."""
-    bins = get_bins(val)
-    _, binned_d, _ = histogram(val, bins=bins)
-
-    bin_edges = np.linspace(np.min(val), np.max(val), len(bins))
-    centers = 0.5 * (bin_edges + np.roll(bin_edges, 1))[:-1]
-    heights = np.diff(bin_edges)
-
-    lefts = -0.5 * binned_d
-    ax.barh(centers, binned_d, height=heights, left=lefts, alpha=shade, **kwargs_shade)
+    return ax


Test patch:
diff --git a/arviz/tests/test_data.py b/arviz/tests/test_data.py
index d55387d..6038d00 100644
--- a/arviz/tests/test_data.py
+++ b/arviz/tests/test_data.py
@@ -303,6 +303,22 @@ def test_concat_bad():
         concat(idata3, idata, dim="chain")
 
 
+def test_inference_concat_keeps_all_fields():
+    """From failures observed in issue #907"""
+    idata1 = from_dict(posterior={"A": [1, 2, 3, 4]}, sample_stats={"B": [2, 3, 4, 5]})
+    idata2 = from_dict(prior={"C": [1, 2, 3, 4]}, observed_data={"D": [2, 3, 4, 5]})
+
+    idata_c1 = concat(idata1, idata2)
+    idata_c2 = concat(idata2, idata1)
+
+    test_dict = {"posterior": ["A"], "sample_stats": ["B"], "prior": ["C"], "observed_data": ["D"]}
+
+    fails_c1 = check_multiple_attrs(test_dict, idata_c1)
+    assert not fails_c1
+    fails_c2 = check_multiple_attrs(test_dict, idata_c2)
+    assert not fails_c2
+
+
 @pytest.mark.parametrize("inplace", [True, False])
 def test_sel_method(inplace):
     data = np.random.normal(size=(4, 500, 8))
diff --git a/arviz/tests/test_plots_bokeh.py b/arviz/tests/test_plots_bokeh.py
index bd4987d..adbf14c 100644
--- a/arviz/tests/test_plots_bokeh.py
+++ b/arviz/tests/test_plots_bokeh.py
@@ -1,6 +1,7 @@
 """Tests use the 'bokeh' backend."""
 # pylint: disable=redefined-outer-name,too-many-lines
 from copy import deepcopy
+import bokeh.plotting as bkp
 from pandas import DataFrame
 import numpy as np
 import pytest
@@ -32,6 +33,7 @@ from ..plots import (
     plot_pair,
     plot_trace,
     plot_parallel,
+    plot_violin,
 )
 from ..stats import compare, loo, waic
 
@@ -749,3 +751,27 @@ def test_plot_parallel_exception(models, var_names):
     """Ensure that correct exception is raised when one variable is passed."""
     with pytest.raises(ValueError):
         assert plot_parallel(models.model_1, var_names=var_names, norm_method="foo")
+
+
+@pytest.mark.parametrize("var_names", (None, "mu", ["mu", "tau"]))
+def test_plot_violin(models, var_names):
+    axes = plot_violin(models.model_1, var_names=var_names, backend="bokeh", show=False)
+    assert axes.shape
+
+
+def test_plot_violin_ax(models):
+    ax = bkp.figure()
+    axes = plot_violin(models.model_1, var_names="mu", ax=ax, backend="bokeh", show=False)
+    assert axes.shape
+
+
+def test_plot_violin_layout(models):
+    axes = plot_violin(
+        models.model_1, var_names=["mu", "tau"], sharey=False, backend="bokeh", show=False
+    )
+    assert axes.shape
+
+
+def test_plot_violin_discrete(discrete_model):
+    axes = plot_violin(discrete_model, backend="bokeh", show=False)
+    assert axes.shape