We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Ordering of variables after coords set
## Short Description

I'm trying to use the coords/dims to set labels on posterior variables from a stan fit and the order of the dimensions gets shuffled when I set the labels (not sure what determines the order). Is there a way to (1) retain the existing order (A,B,C...) and (2) sort the order according to a custom index that I provide (e.g. pass a index array like [2,1,0,3, ...] to have the plot appear in the order [C, B, A, D ...])? 

Sorry if this is covered in the documentation somewhere, I didn't see it.




Without setting any coords/dims, the variables appear in the same order as one would expect from the stan code.

![test1](https://user-images.githubusercontent.com/12652166/51454598-838ab300-1d13-11e9-9d8d-fd42e3be07b2.png)



Setting the coords/dims with the code below changes the order to this.

![test2](https://user-images.githubusercontent.com/12652166/51454545-3b6b9080-1d13-11e9-8417-6e9a029e8803.png)



## Code Example or link

```
azdata = az.from_pystan(posterior=fit, observed_data=['Y'], coords={'betas': ['A', 'B', 'C', 'D', 'E', 'F', 'G']}, dims={'b': ['betas']})
	
axes = az.plot_forest(azdata, var_names=('b'))
```
Using arviz 0.3.2


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `arviz/plots/plot_utils.py`
Content:
```
1 """Utilities for plotting."""
2 from itertools import product
3 
4 import numpy as np
5 import matplotlib.pyplot as plt
6 import matplotlib as mpl
7 import xarray as xr
8 
9 
10 def make_2d(ary):
11     """Convert any array into a 2d numpy array.
12 
13     In case the array is already more than 2 dimensional, will ravel the
14     dimensions after the first.
15     """
16     dim_0, *_ = np.atleast_1d(ary).shape
17     return ary.reshape(dim_0, -1, order="F")
18 
19 
20 def _scale_fig_size(figsize, textsize, rows=1, cols=1):
21     """Scale figure properties according to rows and cols.
22 
23     Parameters
24     ----------
25     figsize : float or None
26         Size of figure in inches
27     textsize : float or None
28         fontsize
29     rows : int
30         Number of rows
31     cols : int
32         Number of columns
33 
34     Returns
35     -------
36     figsize : float or None
37         Size of figure in inches
38     ax_labelsize : int
39         fontsize for axes label
40     titlesize : int
41         fontsize for title
42     xt_labelsize : int
43         fontsize for axes ticks
44     linewidth : int
45         linewidth
46     markersize : int
47         markersize
48     """
49     params = mpl.rcParams
50     rc_width, rc_height = tuple(params["figure.figsize"])
51     rc_ax_labelsize = params["axes.labelsize"]
52     rc_titlesize = params["axes.titlesize"]
53     rc_xt_labelsize = params["xtick.labelsize"]
54     rc_linewidth = params["lines.linewidth"]
55     rc_markersize = params["lines.markersize"]
56     if isinstance(rc_ax_labelsize, str):
57         rc_ax_labelsize = 15
58     if isinstance(rc_titlesize, str):
59         rc_titlesize = 16
60     if isinstance(rc_xt_labelsize, str):
61         rc_xt_labelsize = 14
62 
63     if figsize is None:
64         width, height = rc_width, rc_height
65         sff = 1 if (rows == cols == 1) else 1.15
66         width = width * cols * sff
67         height = height * rows * sff
68     else:
69         width, height = figsize
70 
71     if textsize is not None:
72         scale_factor = textsize / rc_xt_labelsize
73     elif rows == cols == 1:
74         scale_factor = ((width * height) / (rc_width * rc_height)) ** 0.5
75     else:
76         scale_factor = 1
77 
78     ax_labelsize = rc_ax_labelsize * scale_factor
79     titlesize = rc_titlesize * scale_factor
80     xt_labelsize = rc_xt_labelsize * scale_factor
81     linewidth = rc_linewidth * scale_factor
82     markersize = rc_markersize * scale_factor
83 
84     return (width, height), ax_labelsize, titlesize, xt_labelsize, linewidth, markersize
85 
86 
87 def get_bins(ary, max_bins=50, fenceposts=2):
88     """Compute number of bins (or ticks).
89 
90     Parameters
91     ----------
92     ary : numpy.array
93         array to be binned
94     max_bins : int
95         maximum number of bins
96     fenceposts : int
97         when computing bins, this should be 2, when computing ticks this should be 1.
98     """
99     x_max, x_min = ary.max(), ary.min()
100     x_range = x_max - x_min
101     if x_range > max_bins:
102         bins = range(x_min, x_max + fenceposts, max(1, int(x_range / 10)))
103     else:
104         bins = range(x_min, x_max + fenceposts)
105     return bins
106 
107 
108 def default_grid(n_items, max_cols=4, min_cols=3):  # noqa: D202
109     """Make a grid for subplots.
110 
111     Tries to get as close to sqrt(n_items) x sqrt(n_items) as it can,
112     but allows for custom logic
113 
114     Parameters
115     ----------
116     n_items : int
117         Number of panels required
118     max_cols : int
119         Maximum number of columns, inclusive
120     min_cols : int
121         Minimum number of columns, inclusive
122 
123     Returns
124     -------
125     (int, int)
126         Rows and columns, so that rows * columns >= n_items
127     """
128 
129     def in_bounds(val):
130         return np.clip(val, min_cols, max_cols)
131 
132     if n_items <= max_cols:
133         return 1, n_items
134     ideal = in_bounds(round(n_items ** 0.5))
135 
136     for offset in (0, 1, -1, 2, -2):
137         cols = in_bounds(ideal + offset)
138         rows, extra = divmod(n_items, cols)
139         if extra == 0:
140             return rows, cols
141     return n_items // ideal + 1, ideal
142 
143 
144 def _create_axes_grid(length_plotters, rows, cols, **kwargs):
145     """Create figure and axes for grids with multiple plots.
146 
147     Parameters
148     ----------
149     n_items : int
150         Number of panels required
151     rows : int
152         Number of rows
153     cols : int
154         Number of columns
155 
156     Returns
157     -------
158     fig : matplotlib figure
159     ax : matplotlib axes
160     """
161     kwargs.setdefault("constrained_layout", True)
162     fig, ax = plt.subplots(rows, cols, **kwargs)
163     ax = np.ravel(ax)
164     extra = (rows * cols) - length_plotters
165     if extra:
166         for i in range(1, extra + 1):
167             ax[-i].set_axis_off()
168         ax = ax[:-extra]
169     return fig, ax
170 
171 
172 def selection_to_string(selection):
173     """Convert dictionary of coordinates to a string for labels.
174 
175     Parameters
176     ----------
177     selection : dict[Any] -> Any
178 
179     Returns
180     -------
181     str
182         key1: value1, key2: value2, ...
183     """
184     return ", ".join(["{}".format(v) for _, v in selection.items()])
185 
186 
187 def make_label(var_name, selection, position="below"):
188     """Consistent labelling for plots.
189 
190     Parameters
191     ----------
192     var_name : str
193        Name of the variable
194 
195     selection : dict[Any] -> Any
196         Coordinates of the variable
197     position : whether to position the coordinates' label "below" (default) or "beside" the name
198                of the variable
199 
200     Returns
201     -------
202     label
203         A text representation of the label
204     """
205     if selection:
206         sel = selection_to_string(selection)
207         if position == "below":
208             sep = "\n"
209         elif position == "beside":
210             sep = " "
211     else:
212         sep = sel = ""
213     return "{}{}{}".format(var_name, sep, sel)
214 
215 
216 def xarray_var_iter(data, var_names=None, combined=False, skip_dims=None, reverse_selections=False):
217     """Convert xarray data to an iterator over vectors.
218 
219     Iterates over each var_name and all of its coordinates, returning the 1d
220     data.
221 
222     Parameters
223     ----------
224     data : xarray.Dataset
225         Posterior data in an xarray
226 
227     var_names : iterator of strings (optional)
228         Should be a subset of data.data_vars. Defaults to all of them.
229 
230     combined : bool
231         Whether to combine chains or leave them separate
232 
233     skip_dims : set
234         dimensions to not iterate over
235 
236     reverse_selections : bool
237         Whether to reverse selections before iterating.
238 
239     Returns
240     -------
241     Iterator of (str, dict(str, any), np.array)
242         The string is the variable name, the dictionary are coordinate names to values,
243         and the array are the values of the variable at those coordinates.
244     """
245     if skip_dims is None:
246         skip_dims = set()
247 
248     if combined:
249         skip_dims = skip_dims.union({"chain", "draw"})
250     else:
251         skip_dims.add("draw")
252 
253     if var_names is None:
254         if isinstance(data, xr.Dataset):
255             var_names = list(data.data_vars)
256         elif isinstance(data, xr.DataArray):
257             var_names = [data.name]
258             data = {data.name: data}
259 
260     for var_name in var_names:
261         if var_name in data:
262             new_dims = [dim for dim in data[var_name].dims if dim not in skip_dims]
263             vals = [set(data[var_name][dim].values) for dim in new_dims]
264             dims = [{k: v for k, v in zip(new_dims, prod)} for prod in product(*vals)]
265             if reverse_selections:
266                 dims = reversed(dims)
267 
268             for selection in dims:
269                 yield var_name, selection, data[var_name].sel(**selection).values
270 
271 
272 def xarray_to_ndarray(data, *, var_names=None, combined=True):
273     """Take xarray data and unpacks into variables and data into list and numpy array respectively.
274 
275     Assumes that chain and draw are in coordinates
276 
277     Parameters
278     ----------
279     data: xarray.DataSet
280         Data in an xarray from an InferenceData object. Examples include posterior or sample_stats
281 
282     var_names: iter
283         Should be a subset of data.data_vars not including chain and draws. Defaults to all of them
284 
285     combined: bool
286         Whether to combine chain into one array
287 
288     Returns
289     -------
290     var_names: list
291         List of variable names
292     data: np.array
293         Data values
294     """
295     unpacked_data, unpacked_var_names, = [], []
296 
297     # Merge chains and variables
298     for var_name, selection, data_array in xarray_var_iter(
299         data, var_names=var_names, combined=combined
300     ):
301         unpacked_data.append(data_array.flatten())
302         unpacked_var_names.append(make_label(var_name, selection))
303 
304     return unpacked_var_names, np.array(unpacked_data)
305 
306 
307 def get_coords(data, coords):
308     """Subselects xarray dataset object to provided coords. Raises exception if fails.
309 
310     Raises
311     ------
312     ValueError
313         If coords name are not available in data
314 
315     KeyError
316         If coords dims are not available in data
317 
318     Returns
319     -------
320     data: xarray
321         xarray.Dataset object
322     """
323     try:
324         return data.sel(**coords)
325 
326     except ValueError:
327         invalid_coords = set(coords.keys()) - set(data.coords.keys())
328         raise ValueError("Coords {} are invalid coordinate keys".format(invalid_coords))
329 
330     except KeyError as err:
331         raise KeyError(
332             (
333                 "Coords should follow mapping format {{coord_name:[dim1, dim2]}}. "
334                 "Check that coords structure is correct and"
335                 " dimensions are valid. {}"
336             ).format(err)
337         )
338
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/arviz/plots/plot_utils.py b/arviz/plots/plot_utils.py
--- a/arviz/plots/plot_utils.py
+++ b/arviz/plots/plot_utils.py
@@ -260,7 +260,7 @@
     for var_name in var_names:
         if var_name in data:
             new_dims = [dim for dim in data[var_name].dims if dim not in skip_dims]
-            vals = [set(data[var_name][dim].values) for dim in new_dims]
+            vals = [np.unique(data[var_name][dim].values) for dim in new_dims]
             dims = [{k: v for k, v in zip(new_dims, prod)} for prod in product(*vals)]
             if reverse_selections:
                 dims = reversed(dims)

{"golden_diff": "diff --git a/arviz/plots/plot_utils.py b/arviz/plots/plot_utils.py\n--- a/arviz/plots/plot_utils.py\n+++ b/arviz/plots/plot_utils.py\n@@ -260,7 +260,7 @@\n     for var_name in var_names:\n         if var_name in data:\n             new_dims = [dim for dim in data[var_name].dims if dim not in skip_dims]\n-            vals = [set(data[var_name][dim].values) for dim in new_dims]\n+            vals = [np.unique(data[var_name][dim].values) for dim in new_dims]\n             dims = [{k: v for k, v in zip(new_dims, prod)} for prod in product(*vals)]\n             if reverse_selections:\n                 dims = reversed(dims)\n", "issue": "Ordering of variables after coords set\n## Short Description\r\n\r\nI'm trying to use the coords/dims to set labels on posterior variables from a stan fit and the order of the dimensions gets shuffled when I set the labels (not sure what determines the order). Is there a way to (1) retain the existing order (A,B,C...) and (2) sort the order according to a custom index that I provide (e.g. pass a index array like [2,1,0,3, ...] to have the plot appear in the order [C, B, A, D ...])? \r\n\r\nSorry if this is covered in the documentation somewhere, I didn't see it.\r\n\r\n\r\n\r\n\r\nWithout setting any coords/dims, the variables appear in the same order as one would expect from the stan code.\r\n\r\n![test1](https://user-images.githubusercontent.com/12652166/51454598-838ab300-1d13-11e9-9d8d-fd42e3be07b2.png)\r\n\r\n\r\n\r\nSetting the coords/dims with the code below changes the order to this.\r\n\r\n![test2](https://user-images.githubusercontent.com/12652166/51454545-3b6b9080-1d13-11e9-8417-6e9a029e8803.png)\r\n\r\n\r\n\r\n## Code Example or link\r\n\r\n```\r\nazdata = az.from_pystan(posterior=fit, observed_data=['Y'], coords={'betas': ['A', 'B', 'C', 'D', 'E', 'F', 'G']}, dims={'b': ['betas']})\r\n\t\r\naxes = az.plot_forest(azdata, var_names=('b'))\r\n```\r\nUsing arviz 0.3.2\r\n\n", "before_files": [{"content": "\"\"\"Utilities for plotting.\"\"\"\nfrom itertools import product\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport xarray as xr\n\n\ndef make_2d(ary):\n    \"\"\"Convert any array into a 2d numpy array.\n\n    In case the array is already more than 2 dimensional, will ravel the\n    dimensions after the first.\n    \"\"\"\n    dim_0, *_ = np.atleast_1d(ary).shape\n    return ary.reshape(dim_0, -1, order=\"F\")\n\n\ndef _scale_fig_size(figsize, textsize, rows=1, cols=1):\n    \"\"\"Scale figure properties according to rows and cols.\n\n    Parameters\n    ----------\n    figsize : float or None\n        Size of figure in inches\n    textsize : float or None\n        fontsize\n    rows : int\n        Number of rows\n    cols : int\n        Number of columns\n\n    Returns\n    -------\n    figsize : float or None\n        Size of figure in inches\n    ax_labelsize : int\n        fontsize for axes label\n    titlesize : int\n        fontsize for title\n    xt_labelsize : int\n        fontsize for axes ticks\n    linewidth : int\n        linewidth\n    markersize : int\n        markersize\n    \"\"\"\n    params = mpl.rcParams\n    rc_width, rc_height = tuple(params[\"figure.figsize\"])\n    rc_ax_labelsize = params[\"axes.labelsize\"]\n    rc_titlesize = params[\"axes.titlesize\"]\n    rc_xt_labelsize = params[\"xtick.labelsize\"]\n    rc_linewidth = params[\"lines.linewidth\"]\n    rc_markersize = params[\"lines.markersize\"]\n    if isinstance(rc_ax_labelsize, str):\n        rc_ax_labelsize = 15\n    if isinstance(rc_titlesize, str):\n        rc_titlesize = 16\n    if isinstance(rc_xt_labelsize, str):\n        rc_xt_labelsize = 14\n\n    if figsize is None:\n        width, height = rc_width, rc_height\n        sff = 1 if (rows == cols == 1) else 1.15\n        width = width * cols * sff\n        height = height * rows * sff\n    else:\n        width, height = figsize\n\n    if textsize is not None:\n        scale_factor = textsize / rc_xt_labelsize\n    elif rows == cols == 1:\n        scale_factor = ((width * height) / (rc_width * rc_height)) ** 0.5\n    else:\n        scale_factor = 1\n\n    ax_labelsize = rc_ax_labelsize * scale_factor\n    titlesize = rc_titlesize * scale_factor\n    xt_labelsize = rc_xt_labelsize * scale_factor\n    linewidth = rc_linewidth * scale_factor\n    markersize = rc_markersize * scale_factor\n\n    return (width, height), ax_labelsize, titlesize, xt_labelsize, linewidth, markersize\n\n\ndef get_bins(ary, max_bins=50, fenceposts=2):\n    \"\"\"Compute number of bins (or ticks).\n\n    Parameters\n    ----------\n    ary : numpy.array\n        array to be binned\n    max_bins : int\n        maximum number of bins\n    fenceposts : int\n        when computing bins, this should be 2, when computing ticks this should be 1.\n    \"\"\"\n    x_max, x_min = ary.max(), ary.min()\n    x_range = x_max - x_min\n    if x_range > max_bins:\n        bins = range(x_min, x_max + fenceposts, max(1, int(x_range / 10)))\n    else:\n        bins = range(x_min, x_max + fenceposts)\n    return bins\n\n\ndef default_grid(n_items, max_cols=4, min_cols=3):  # noqa: D202\n    \"\"\"Make a grid for subplots.\n\n    Tries to get as close to sqrt(n_items) x sqrt(n_items) as it can,\n    but allows for custom logic\n\n    Parameters\n    ----------\n    n_items : int\n        Number of panels required\n    max_cols : int\n        Maximum number of columns, inclusive\n    min_cols : int\n        Minimum number of columns, inclusive\n\n    Returns\n    -------\n    (int, int)\n        Rows and columns, so that rows * columns >= n_items\n    \"\"\"\n\n    def in_bounds(val):\n        return np.clip(val, min_cols, max_cols)\n\n    if n_items <= max_cols:\n        return 1, n_items\n    ideal = in_bounds(round(n_items ** 0.5))\n\n    for offset in (0, 1, -1, 2, -2):\n        cols = in_bounds(ideal + offset)\n        rows, extra = divmod(n_items, cols)\n        if extra == 0:\n            return rows, cols\n    return n_items // ideal + 1, ideal\n\n\ndef _create_axes_grid(length_plotters, rows, cols, **kwargs):\n    \"\"\"Create figure and axes for grids with multiple plots.\n\n    Parameters\n    ----------\n    n_items : int\n        Number of panels required\n    rows : int\n        Number of rows\n    cols : int\n        Number of columns\n\n    Returns\n    -------\n    fig : matplotlib figure\n    ax : matplotlib axes\n    \"\"\"\n    kwargs.setdefault(\"constrained_layout\", True)\n    fig, ax = plt.subplots(rows, cols, **kwargs)\n    ax = np.ravel(ax)\n    extra = (rows * cols) - length_plotters\n    if extra:\n        for i in range(1, extra + 1):\n            ax[-i].set_axis_off()\n        ax = ax[:-extra]\n    return fig, ax\n\n\ndef selection_to_string(selection):\n    \"\"\"Convert dictionary of coordinates to a string for labels.\n\n    Parameters\n    ----------\n    selection : dict[Any] -> Any\n\n    Returns\n    -------\n    str\n        key1: value1, key2: value2, ...\n    \"\"\"\n    return \", \".join([\"{}\".format(v) for _, v in selection.items()])\n\n\ndef make_label(var_name, selection, position=\"below\"):\n    \"\"\"Consistent labelling for plots.\n\n    Parameters\n    ----------\n    var_name : str\n       Name of the variable\n\n    selection : dict[Any] -> Any\n        Coordinates of the variable\n    position : whether to position the coordinates' label \"below\" (default) or \"beside\" the name\n               of the variable\n\n    Returns\n    -------\n    label\n        A text representation of the label\n    \"\"\"\n    if selection:\n        sel = selection_to_string(selection)\n        if position == \"below\":\n            sep = \"\\n\"\n        elif position == \"beside\":\n            sep = \" \"\n    else:\n        sep = sel = \"\"\n    return \"{}{}{}\".format(var_name, sep, sel)\n\n\ndef xarray_var_iter(data, var_names=None, combined=False, skip_dims=None, reverse_selections=False):\n    \"\"\"Convert xarray data to an iterator over vectors.\n\n    Iterates over each var_name and all of its coordinates, returning the 1d\n    data.\n\n    Parameters\n    ----------\n    data : xarray.Dataset\n        Posterior data in an xarray\n\n    var_names : iterator of strings (optional)\n        Should be a subset of data.data_vars. Defaults to all of them.\n\n    combined : bool\n        Whether to combine chains or leave them separate\n\n    skip_dims : set\n        dimensions to not iterate over\n\n    reverse_selections : bool\n        Whether to reverse selections before iterating.\n\n    Returns\n    -------\n    Iterator of (str, dict(str, any), np.array)\n        The string is the variable name, the dictionary are coordinate names to values,\n        and the array are the values of the variable at those coordinates.\n    \"\"\"\n    if skip_dims is None:\n        skip_dims = set()\n\n    if combined:\n        skip_dims = skip_dims.union({\"chain\", \"draw\"})\n    else:\n        skip_dims.add(\"draw\")\n\n    if var_names is None:\n        if isinstance(data, xr.Dataset):\n            var_names = list(data.data_vars)\n        elif isinstance(data, xr.DataArray):\n            var_names = [data.name]\n            data = {data.name: data}\n\n    for var_name in var_names:\n        if var_name in data:\n            new_dims = [dim for dim in data[var_name].dims if dim not in skip_dims]\n            vals = [set(data[var_name][dim].values) for dim in new_dims]\n            dims = [{k: v for k, v in zip(new_dims, prod)} for prod in product(*vals)]\n            if reverse_selections:\n                dims = reversed(dims)\n\n            for selection in dims:\n                yield var_name, selection, data[var_name].sel(**selection).values\n\n\ndef xarray_to_ndarray(data, *, var_names=None, combined=True):\n    \"\"\"Take xarray data and unpacks into variables and data into list and numpy array respectively.\n\n    Assumes that chain and draw are in coordinates\n\n    Parameters\n    ----------\n    data: xarray.DataSet\n        Data in an xarray from an InferenceData object. Examples include posterior or sample_stats\n\n    var_names: iter\n        Should be a subset of data.data_vars not including chain and draws. Defaults to all of them\n\n    combined: bool\n        Whether to combine chain into one array\n\n    Returns\n    -------\n    var_names: list\n        List of variable names\n    data: np.array\n        Data values\n    \"\"\"\n    unpacked_data, unpacked_var_names, = [], []\n\n    # Merge chains and variables\n    for var_name, selection, data_array in xarray_var_iter(\n        data, var_names=var_names, combined=combined\n    ):\n        unpacked_data.append(data_array.flatten())\n        unpacked_var_names.append(make_label(var_name, selection))\n\n    return unpacked_var_names, np.array(unpacked_data)\n\n\ndef get_coords(data, coords):\n    \"\"\"Subselects xarray dataset object to provided coords. Raises exception if fails.\n\n    Raises\n    ------\n    ValueError\n        If coords name are not available in data\n\n    KeyError\n        If coords dims are not available in data\n\n    Returns\n    -------\n    data: xarray\n        xarray.Dataset object\n    \"\"\"\n    try:\n        return data.sel(**coords)\n\n    except ValueError:\n        invalid_coords = set(coords.keys()) - set(data.coords.keys())\n        raise ValueError(\"Coords {} are invalid coordinate keys\".format(invalid_coords))\n\n    except KeyError as err:\n        raise KeyError(\n            (\n                \"Coords should follow mapping format {{coord_name:[dim1, dim2]}}. \"\n                \"Check that coords structure is correct and\"\n                \" dimensions are valid. {}\"\n            ).format(err)\n        )\n", "path": "arviz/plots/plot_utils.py"}], "after_files": [{"content": "\"\"\"Utilities for plotting.\"\"\"\nfrom itertools import product\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport xarray as xr\n\n\ndef make_2d(ary):\n    \"\"\"Convert any array into a 2d numpy array.\n\n    In case the array is already more than 2 dimensional, will ravel the\n    dimensions after the first.\n    \"\"\"\n    dim_0, *_ = np.atleast_1d(ary).shape\n    return ary.reshape(dim_0, -1, order=\"F\")\n\n\ndef _scale_fig_size(figsize, textsize, rows=1, cols=1):\n    \"\"\"Scale figure properties according to rows and cols.\n\n    Parameters\n    ----------\n    figsize : float or None\n        Size of figure in inches\n    textsize : float or None\n        fontsize\n    rows : int\n        Number of rows\n    cols : int\n        Number of columns\n\n    Returns\n    -------\n    figsize : float or None\n        Size of figure in inches\n    ax_labelsize : int\n        fontsize for axes label\n    titlesize : int\n        fontsize for title\n    xt_labelsize : int\n        fontsize for axes ticks\n    linewidth : int\n        linewidth\n    markersize : int\n        markersize\n    \"\"\"\n    params = mpl.rcParams\n    rc_width, rc_height = tuple(params[\"figure.figsize\"])\n    rc_ax_labelsize = params[\"axes.labelsize\"]\n    rc_titlesize = params[\"axes.titlesize\"]\n    rc_xt_labelsize = params[\"xtick.labelsize\"]\n    rc_linewidth = params[\"lines.linewidth\"]\n    rc_markersize = params[\"lines.markersize\"]\n    if isinstance(rc_ax_labelsize, str):\n        rc_ax_labelsize = 15\n    if isinstance(rc_titlesize, str):\n        rc_titlesize = 16\n    if isinstance(rc_xt_labelsize, str):\n        rc_xt_labelsize = 14\n\n    if figsize is None:\n        width, height = rc_width, rc_height\n        sff = 1 if (rows == cols == 1) else 1.15\n        width = width * cols * sff\n        height = height * rows * sff\n    else:\n        width, height = figsize\n\n    if textsize is not None:\n        scale_factor = textsize / rc_xt_labelsize\n    elif rows == cols == 1:\n        scale_factor = ((width * height) / (rc_width * rc_height)) ** 0.5\n    else:\n        scale_factor = 1\n\n    ax_labelsize = rc_ax_labelsize * scale_factor\n    titlesize = rc_titlesize * scale_factor\n    xt_labelsize = rc_xt_labelsize * scale_factor\n    linewidth = rc_linewidth * scale_factor\n    markersize = rc_markersize * scale_factor\n\n    return (width, height), ax_labelsize, titlesize, xt_labelsize, linewidth, markersize\n\n\ndef get_bins(ary, max_bins=50, fenceposts=2):\n    \"\"\"Compute number of bins (or ticks).\n\n    Parameters\n    ----------\n    ary : numpy.array\n        array to be binned\n    max_bins : int\n        maximum number of bins\n    fenceposts : int\n        when computing bins, this should be 2, when computing ticks this should be 1.\n    \"\"\"\n    x_max, x_min = ary.max(), ary.min()\n    x_range = x_max - x_min\n    if x_range > max_bins:\n        bins = range(x_min, x_max + fenceposts, max(1, int(x_range / 10)))\n    else:\n        bins = range(x_min, x_max + fenceposts)\n    return bins\n\n\ndef default_grid(n_items, max_cols=4, min_cols=3):  # noqa: D202\n    \"\"\"Make a grid for subplots.\n\n    Tries to get as close to sqrt(n_items) x sqrt(n_items) as it can,\n    but allows for custom logic\n\n    Parameters\n    ----------\n    n_items : int\n        Number of panels required\n    max_cols : int\n        Maximum number of columns, inclusive\n    min_cols : int\n        Minimum number of columns, inclusive\n\n    Returns\n    -------\n    (int, int)\n        Rows and columns, so that rows * columns >= n_items\n    \"\"\"\n\n    def in_bounds(val):\n        return np.clip(val, min_cols, max_cols)\n\n    if n_items <= max_cols:\n        return 1, n_items\n    ideal = in_bounds(round(n_items ** 0.5))\n\n    for offset in (0, 1, -1, 2, -2):\n        cols = in_bounds(ideal + offset)\n        rows, extra = divmod(n_items, cols)\n        if extra == 0:\n            return rows, cols\n    return n_items // ideal + 1, ideal\n\n\ndef _create_axes_grid(length_plotters, rows, cols, **kwargs):\n    \"\"\"Create figure and axes for grids with multiple plots.\n\n    Parameters\n    ----------\n    n_items : int\n        Number of panels required\n    rows : int\n        Number of rows\n    cols : int\n        Number of columns\n\n    Returns\n    -------\n    fig : matplotlib figure\n    ax : matplotlib axes\n    \"\"\"\n    kwargs.setdefault(\"constrained_layout\", True)\n    fig, ax = plt.subplots(rows, cols, **kwargs)\n    ax = np.ravel(ax)\n    extra = (rows * cols) - length_plotters\n    if extra:\n        for i in range(1, extra + 1):\n            ax[-i].set_axis_off()\n        ax = ax[:-extra]\n    return fig, ax\n\n\ndef selection_to_string(selection):\n    \"\"\"Convert dictionary of coordinates to a string for labels.\n\n    Parameters\n    ----------\n    selection : dict[Any] -> Any\n\n    Returns\n    -------\n    str\n        key1: value1, key2: value2, ...\n    \"\"\"\n    return \", \".join([\"{}\".format(v) for _, v in selection.items()])\n\n\ndef make_label(var_name, selection, position=\"below\"):\n    \"\"\"Consistent labelling for plots.\n\n    Parameters\n    ----------\n    var_name : str\n       Name of the variable\n\n    selection : dict[Any] -> Any\n        Coordinates of the variable\n    position : whether to position the coordinates' label \"below\" (default) or \"beside\" the name\n               of the variable\n\n    Returns\n    -------\n    label\n        A text representation of the label\n    \"\"\"\n    if selection:\n        sel = selection_to_string(selection)\n        if position == \"below\":\n            sep = \"\\n\"\n        elif position == \"beside\":\n            sep = \" \"\n    else:\n        sep = sel = \"\"\n    return \"{}{}{}\".format(var_name, sep, sel)\n\n\ndef xarray_var_iter(data, var_names=None, combined=False, skip_dims=None, reverse_selections=False):\n    \"\"\"Convert xarray data to an iterator over vectors.\n\n    Iterates over each var_name and all of its coordinates, returning the 1d\n    data.\n\n    Parameters\n    ----------\n    data : xarray.Dataset\n        Posterior data in an xarray\n\n    var_names : iterator of strings (optional)\n        Should be a subset of data.data_vars. Defaults to all of them.\n\n    combined : bool\n        Whether to combine chains or leave them separate\n\n    skip_dims : set\n        dimensions to not iterate over\n\n    reverse_selections : bool\n        Whether to reverse selections before iterating.\n\n    Returns\n    -------\n    Iterator of (str, dict(str, any), np.array)\n        The string is the variable name, the dictionary are coordinate names to values,\n        and the array are the values of the variable at those coordinates.\n    \"\"\"\n    if skip_dims is None:\n        skip_dims = set()\n\n    if combined:\n        skip_dims = skip_dims.union({\"chain\", \"draw\"})\n    else:\n        skip_dims.add(\"draw\")\n\n    if var_names is None:\n        if isinstance(data, xr.Dataset):\n            var_names = list(data.data_vars)\n        elif isinstance(data, xr.DataArray):\n            var_names = [data.name]\n            data = {data.name: data}\n\n    for var_name in var_names:\n        if var_name in data:\n            new_dims = [dim for dim in data[var_name].dims if dim not in skip_dims]\n            vals = [np.unique(data[var_name][dim].values) for dim in new_dims]\n            dims = [{k: v for k, v in zip(new_dims, prod)} for prod in product(*vals)]\n            if reverse_selections:\n                dims = reversed(dims)\n\n            for selection in dims:\n                yield var_name, selection, data[var_name].sel(**selection).values\n\n\ndef xarray_to_ndarray(data, *, var_names=None, combined=True):\n    \"\"\"Take xarray data and unpacks into variables and data into list and numpy array respectively.\n\n    Assumes that chain and draw are in coordinates\n\n    Parameters\n    ----------\n    data: xarray.DataSet\n        Data in an xarray from an InferenceData object. Examples include posterior or sample_stats\n\n    var_names: iter\n        Should be a subset of data.data_vars not including chain and draws. Defaults to all of them\n\n    combined: bool\n        Whether to combine chain into one array\n\n    Returns\n    -------\n    var_names: list\n        List of variable names\n    data: np.array\n        Data values\n    \"\"\"\n    unpacked_data, unpacked_var_names, = [], []\n\n    # Merge chains and variables\n    for var_name, selection, data_array in xarray_var_iter(\n        data, var_names=var_names, combined=combined\n    ):\n        unpacked_data.append(data_array.flatten())\n        unpacked_var_names.append(make_label(var_name, selection))\n\n    return unpacked_var_names, np.array(unpacked_data)\n\n\ndef get_coords(data, coords):\n    \"\"\"Subselects xarray dataset object to provided coords. Raises exception if fails.\n\n    Raises\n    ------\n    ValueError\n        If coords name are not available in data\n\n    KeyError\n        If coords dims are not available in data\n\n    Returns\n    -------\n    data: xarray\n        xarray.Dataset object\n    \"\"\"\n    try:\n        return data.sel(**coords)\n\n    except ValueError:\n        invalid_coords = set(coords.keys()) - set(data.coords.keys())\n        raise ValueError(\"Coords {} are invalid coordinate keys\".format(invalid_coords))\n\n    except KeyError as err:\n        raise KeyError(\n            (\n                \"Coords should follow mapping format {{coord_name:[dim1, dim2]}}. \"\n                \"Check that coords structure is correct and\"\n                \" dimensions are valid. {}\"\n            ).format(err)\n        )\n", "path": "arviz/plots/plot_utils.py"}]}