Repository: arviz-devs/arviz

Issue:
Attribute error when observed has multiple dimensions: MultiObservedRV
Here's a Weibull survival model in pymc3:

```
def weibull_lccdf(alpha, beta, x):
    return -(x / beta)**alpha

class Weibull_like(pm.Continuous):
    def __init__(self, alpha, lambd, *args, **kwargs):
       
        self.alpha = tt.as_tensor_variable(alpha)
        self.lambd = tt.as_tensor_variable(lambd)
        
        super().__init__(*args, **kwargs)

    def logp(self, failure, value):
        
        alpha = self.alpha
        beta = 1/tt.exp(self.lambd)

        no_censoring = failure.astype('bool')
        beta_nocens = beta[no_censoring]
        beta_censor = beta[~no_censoring]
        
        uncensored = pm.Weibull.dist(alpha, beta_nocens).logp(value[no_censoring]).sum()
        censored = weibull_lccdf(alpha, beta_censor, value[~no_censoring]).sum()
        
        return uncensored + censored
```

and the setting up the model

```
with pm.Model() as weibull:
    beta0 = pm.Normal('β_0', 0, 100)
    beta1 = pm.Normal('β_1', 0, 100)
    alpha = pm.Gamma('α', 1.0, 1/0.001)
    
    lam_ = pm.Deterministic('λ', beta0 + beta1*(treatment - treatment.mean()))
    
    like = Weibull_like('survival', alpha=alpha, lambd=lam_, observed={'failure':failure, 'value':t})
    
    trace_weibull = pm.sample(tune=1000, draws=3000, chains=2)
```

Running any of plots gives an Attribute error:
```
av.plot_trace(trace_EVM)
```

```
/anaconda3/lib/python3.6/site-packages/arviz/data/io_pymc3.py in <dictcomp>(.0)
    107         model = self.trace._straces[0].model  # pylint: disable=protected-access
    108 
--> 109         observations = {obs.name: obs.observations for obs in model.observed_RVs}
    110         if self.dims is None:
    111             dims = {}

AttributeError: 'MultiObservedRV' object has no attribute 'observations'
```

Poking around a bit, it looks like `MultiObservedRV`, doesn't create an observations parameter but instead saves in self.data:
 
https://github.com/pymc-devs/pymc3/blob/master/pymc3/model.py#L1369

Suggestion on how to fix this? Is it as simple as just patching the PyMC3Converter class where it build the `observations` dict? 

https://github.com/arviz-devs/arviz/blob/master/arviz/data/io_pymc3.py#L110

I'm assuming just doing this will break some of the plots? 


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=h1) Report
> Merging [#1098](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=desc) into [master](https://codecov.io/gh/arviz-devs/arviz/commit/d57b851db7255ce21a8a3fa56c9daf8d0ed58cb6&el=desc) will **decrease** coverage by `14.09%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/arviz-devs/arviz/pull/1098/graphs/tree.svg?width=650&height=150&src=pr&token=KZTdQCEf7t)](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           master    #1098       +/-   ##
===========================================
- Coverage   93.50%   79.41%   -14.10%     
===========================================
  Files          93       93               
  Lines        8773     8855       +82     
===========================================
- Hits         8203     7032     -1171     
- Misses        570     1823     +1253     
```


| [Impacted Files](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [arviz/data/io\_emcee.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19lbWNlZS5weQ==) | `12.50% <0.00%> (-85.84%)` | :arrow_down: |
| [arviz/data/io\_pystan.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19weXN0YW4ucHk=) | `16.47% <0.00%> (-82.56%)` | :arrow_down: |
| [arviz/data/io\_tfp.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb190ZnAucHk=) | `14.42% <0.00%> (-81.74%)` | :arrow_down: |
| [arviz/data/io\_cmdstanpy.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19jbWRzdGFucHkucHk=) | `21.69% <0.00%> (-78.31%)` | :arrow_down: |
| [arviz/data/io\_pyro.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19weXJvLnB5) | `25.92% <0.00%> (-71.17%)` | :arrow_down: |
| [arviz/data/io\_numpyro.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19udW1weXJvLnB5) | `25.23% <0.00%> (-65.95%)` | :arrow_down: |
| [arviz/data/io\_pymc3.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19weW1jMy5weQ==) | `22.64% <0.00%> (-69.73%)` | :arrow_down: |
| [arviz/data/io\_cmdstan.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9pb19jbWRzdGFuLnB5) | `43.77% <0.00%> (-50.93%)` | :arrow_down: |
| [arviz/data/base.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9iYXNlLnB5) | `89.02% <0.00%> (-10.98%)` | :arrow_down: |
| [arviz/data/converters.py](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree#diff-YXJ2aXovZGF0YS9jb252ZXJ0ZXJzLnB5) | `65.15% <0.00%> (-1.52%)` | :arrow_down: |
| ... and [7 more](https://codecov.io/gh/arviz-devs/arviz/pull/1098/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=footer). Last update [d57b851...04c0cf5](https://codecov.io/gh/arviz-devs/arviz/pull/1098?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

canyon289: @OriolAbril Feeling good about this one? Happy to review and merge if so
OriolAbril: Yep, should be ready to merge now. 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7120fe..da7e892 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,18 +3,26 @@
 ## v0.x.x Unreleased
 
 ### New features
+* loo-pit plot. The kde is computed over the data interval (this could be shorter than [0, 1]). The hdi is computed analitically (#1215) 
 
 ### Maintenance and fixes
+* Include data from `MultiObservedRV` to `observed_data` when using
+  `from_pymc3` (#1098)
+
+* Added a note on `plot_pair` when trying to use `plot_kde` on `InferenceData`
+  objects. (#1218)
 
 ### Deprecation
 
 ### Documentation
+* Fixed inconsistent capitalization in `plot_hdi` docstring (#1221)
 
 ## v0.8.3 (2020 May 28)
 ### Maintenance and fixes
 * Restructured internals of `from_pymc3` to handle old pymc3 releases and
   sliced traces and to provide useful warnings (#1211)
 
+
 ## v0.8.2 (2020 May 25)
 ### Maintenance and fixes
 * Fixed bug in `from_pymc3` for sliced `pymc3.MultiTrace` input (#1209)
@@ -276,4 +284,3 @@
 ## v0.3.0 (2018 Dec 14)
 
 * First Beta Release
-
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
new file mode 100644
index 0000000..c57f771
--- /dev/null
+++ b/GOVERNANCE.md
@@ -0,0 +1,205 @@
+# Main Governance Document
+*Note* This Governance has not been implemented yet. Please see Election PR or document for specifics of where we are in establishing governance
+
+The Project
+===========
+
+The ArviZ Project (The Project) is an open source software project
+affiliated with the 501c3 NumFocus Foundation. The goal of The Project is to
+develop open source software and deploy open and public websites and services
+for reproducible, exploratory and interactive computing. The Software developed
+by The Project is released under the Apache 2 open source license,
+developed openly and hosted in public GitHub repositories under the
+[GitHub organization](https://github.com/arviz-devs). Examples of
+Project Software include the ArviZ code and the Documentation, etc. The Services run by the
+Project consist of public websites and web-services that are hosted
+at [https://arviz-devs.github.io/arviz/](https://arviz-devs.github.io/arviz/)
+The Project is developed by a team of distributed developers, called
+Contributors. Contributors are individuals who have contributed code,
+documentation, designs or other work to one or more Project repositories.
+Anyone can be a Contributor. Contributors can be affiliated with any legal
+entity or none. Contributors participate in the project by submitting,
+reviewing and discussing GitHub Pull Requests and Issues and participating in
+open and public Project discussions on GitHub, Slack, and Gitter chat rooms. The foundation of Project participation is openness
+and transparency.
+
+There have been many Contributors to the Project, whose contributions are listed in the
+logs of any of the repositories under the ArviZ-devs organization.
+
+The Project Community consists of all Contributors and Users of the Project.
+Contributors work on behalf of and are responsible to the larger Project
+Community and we strive to keep the barrier between Contributors and Users as
+low as possible.
+
+The Project is formally affiliated with the 501c3 NumFOCUS Foundation
+([http://numfocus.org](http://numfocus.org)),  NumFOCUS is the
+only legal entity that has a formal relationship with the project.
+
+### Governance
+
+This section describes the governance and leadership model of The Project.
+
+The foundations of Project governance are:
+
+-   Openness & Transparency
+-   Active Contribution
+-   Institutional Neutrality
+
+Traditionally, project leadership was unstructured but primarily driven
+by a subset of Core Contributors whose active and consistent
+contributions have been recognized by their receiving “commit rights” to the
+Project GitHub repositories. In general all Project decisions are made through
+consensus among the Core Contributors with input from the Community.
+
+While this approach has served us well, as the Project grows and faces more
+legal and financial decisions and interacts with other institutions, we see a
+need for a more formal governance model. Moving forward The Project leadership
+will consist of a Random Variables Council. We view this governance model as
+the formalization of what we are already doing, rather than a change in
+direction.
+
+
+Community Architecture
+-----------------------
+
+* N Person steering council (Must be odd, will be decided in first election)
+* Core Contributors (of which Council members are also a part of)
+* General Contributors
+
+Anyone working with ArviZ has the responsibility to personally uphold
+the Code of Conduct. Core Contributors have the additional responsibility
+of _enforcing_ the Code of Conduct to maintain a safe community.
+
+
+Random Variables Council
+------------------------
+The Project will have a Steering Council that consists of Core Contributors
+who have produced contributions that are substantial in quality and quantity,
+and sustained over at least one year. The overall role of the Council is to
+ensure, taking input from the Community, the
+long-term well-being of the project, both technically and as a community.
+
+During the everyday project activities, council members participate in all
+discussions, code review and other project activities as peers with all other
+Contributors and the Community. In these everyday activities, Council Members
+do not have any special power or privilege through their membership on the
+Council. However, it is expected that because of the quality and quantity of
+their contributions and their expert knowledge of the Project Software and
+Services that Council Members will provide useful guidance, both technical and
+in terms of project direction, to potentially less experienced contributors.
+
+Council Members will have the responsibility of
+* Removing members, including Council Members, if they are in violation of the Code of Conduct
+* Make decisions when regular community discussion doesn’t produce consensus on an issue in a reasonable time frame.
+* Make decisions about strategic collaborations with other organizations or individuals.
+* Make decisions about the overall scope, vision and direction of the project.
+
+Note that each individual council member does not have the power to unilaterally wield these responsibilities, but the council as a whole must jointly make these decisions. In other words, Council Members are first and foremost Core Contributors, but only when needed they can collectively make decisions for the health of the project.
+
+ArviZ will be holding its first election to determine its initial council in the coming weeks and this document will be updated.
+
+### Private communications of the Council
+
+Unless specifically required, all Council discussions and activities will be
+between public (Github, gitter), and partially public channels (Slack)
+and done in collaboration and discussion with the Core Contributors
+and Community. The Council will have a private channel that will be used
+sparingly and only when a specific matter requires privacy. When private
+communications and decisions are needed, the Council will do its best to
+summarize those to the Community after eliding personal/private/sensitive
+information that should not be posted to the public internet.
+
+### Conflict of interest
+
+It is expected that Council Members will be employed at a wide
+range of companies, universities and non-profit organizations. Because of this,
+it is possible that Members will have conflict of interests. Such conflict of
+interests include, but are not limited to:
+
+-   Financial interests, such as investments, employment or contracting work,
+    outside of The Project that may influence their work on The Project.
+-   Access to proprietary information of their employer that could potentially
+    leak into their work with the Project.
+
+All members of the Council shall disclose to the rest of the
+Council any conflict of interest they may have. Members with a conflict of
+interest in a particular issue may participate in Council discussions on that
+issue, but must recuse themselves from voting on the issue.
+
+### Council Selection Process
+
+#### Eligibility
+* Must be core contributor for at least one year
+
+#### Nominations
+* Nominations are taken over a public github issue ticket over the course of 2 weeks.
+* Only Core Contributors may nominate folks
+* Self Nominations are allowed
+* At the conclusion of the 2 weeks, the list of nominations is posted on the ticket and this ticket is closed.
+
+
+#### Election Process
+
+* Voting occurs over a period of at least 1 week, at the conclusion of the nominations. Voting is blind and mediated by either an application or a third party like Numfocus.
+Each voter can vote zero or more times, once per each candidate. As this is not about ranking but about capabilities, voters vote on a yes/no basis per candidate -- “would I trust this person to lead ArviZ?”.
+* In the event of a tie there will be a runoff election for the tied candidates. To avoid further ties and discriminate more among the tied candidates, this vote will be held by [Majority Judgment](https://en.wikipedia.org/wiki/Majority_judgment): for each candidate, voters judge their suitability for office as either Excellent, Very Good, Good, Acceptable, Poor, or Reject. Multiple candidates may be given the same grade by a voter. The candidate with the highest median grade is the winner.
+* If more than one candidate has the same highest median-grade, the MJ winner is discovered by removing (one-by-one) any grades equal in value to the shared median grade from each tied candidate's total. This is repeated until only one of the previously tied candidates is currently found to have the highest median-grade.
+* If ties are still present after this second round, the winner will be chosen at random. Each person tied will pick a number between 0 and 100, and a random integer will be generated from random.org. The person with the closet circular distance will be selected.
+* At the conclusion of voting, all the results will be posted.
+* The decision about who can vote after the first election is deferred to the council. See below for details
+
+#### Length of Tenure and Reverification
+* Council members term limits are 4 years, after which point their seat will come up for reelection.
+* Each year on April 7th council members will be asked to restate their commitment to being on the council
+* Attempts should be made to reach every council member over at least 2 communication media. For example: email, slack, phone, or github.
+* If a council member does not restate their commitment their seat will be vacated.
+* Inactivity can be determined by lack of substantial contribution, including votes on council, code or discussion contributions, contributions in the community or otherwise.
+* In the event of a vacancy in the council, an election will be held to fill the position.
+* There is no limit on the number of terms a Council Member can serve
+
+#### Vote of No Confidence
+* In exceptional circumstances, council members as well as core contributors may remove a sitting council member via a vote of no confidence. Core contributors can also call for a vote to remove the entire council -- in which case, Council Members do not vote.
+* A no-confidence vote is triggered when a core team member (i.e Council member or Core contributor) calls for one publicly on an appropriate project communication channel, and two other core team members second the proposal. The initial call for a no-confidence vote must specify which type is intended -- whether it is targeting a single member or the council as a whole.
+* The vote lasts for two weeks, and the people taking part in it vary:
+  * If this is a single-member vote called by Core contributors, both Council members and Core contributors vote, and the vote is deemed successful if at least two thirds of voters express a lack of confidence.
+  * If this is a whole-council vote, then it was necessarily called by Core contributors (since Council members can’t remove the whole Council) and only Core contributors vote. The vote is deemed successful if at least two thirds of voters express a lack of confidence.
+  * If this is a single-member vote called by Council Members, only Council Members vote, and the vote is deemed successful if at least half the voters express a lack of confidence. Council Members also have the possibility to call for the whole core team to vote (i.e Council members and Core contributors), although this is not the default option. The threshold for successful vote is also at 50% of voters for this option.
+* If a single-member vote succeeds, then that member is removed from the council and the resulting vacancy can be handled in the usual way.
+* If a whole-council vote succeeds, the council is dissolved and a new council election is triggered immediately.
+
+#### Ejecting Core Contributors
+* Core contributors can be ejected through a simple majority vote by the council
+* Upon ejecting a core contributor the council must publish an issue ticket, or public document detailing the
+  * Violations
+  * Evidence if available
+  * Remediation plan (if necessary)
+  * Signatures majority of council members to validate correctness and accuracy
+* Core contributors can also voluntarily leave the project by notifying the community through a public means or by notifying the entire council.
+
+### One Time Decisions
+* The first election will also include a vote for the number of Council Members, options being 5 and 7. A simple majority will indicate preference.
+  * At the conclusion of the vote this section should be removed
+
+#### Voting Criteria For Future Elections
+Voting for first election is restricted to establish stable governance, and to defer major decision to elected leaders
+* For the first election only the folks in Slack can vote (excluding GSOC students)
+* In the first year, the council must determine voting eligibility for future elections between two criteria:
+  * Those with commit bits
+  * The contributing community at large
+
+### Core Contributors
+Core Contributors are those who have provided consistent and meaningful contributions to ArviZ.
+These can be, but are not limited to, code contributions, community contributions, tutorial
+development etc.
+
+#### Core Contributor Nominations and Confirmation Process
+Current Core Contributors can nominate candidates for consideration by the council. The council
+can make the determination for acceptance with a process of their choosing.
+
+#### Current Core Contributors
+* Will be updated with Core Contributor list during first election
+
+#### Core Contributor Responsibilities
+* Enforce code of conduct
+* Maintain a check against Council
+
diff --git a/arviz/data/io_pymc3.py b/arviz/data/io_pymc3.py
index 879d24f..891210a 100644
--- a/arviz/data/io_pymc3.py
+++ b/arviz/data/io_pymc3.py
@@ -1,7 +1,7 @@
 """PyMC3-specific conversion code."""
 import logging
 import warnings
-from typing import Dict, List, Any, Optional, Iterable, Union, TYPE_CHECKING, Tuple
+from typing import Dict, List, Tuple, Any, Optional, Iterable, Union, TYPE_CHECKING
 from types import ModuleType
 
 import numpy as np
@@ -149,18 +149,21 @@ class PyMC3Converter:  # pylint: disable=too-many-instance-attributes
 
         self.coords = coords
         self.dims = dims
-        self.observations = self.find_observations()
+        self.observations, self.multi_observations = self.find_observations()
 
-    def find_observations(self) -> Optional[Dict[str, Var]]:
+    def find_observations(self) -> Tuple[Optional[Dict[str, Var]], Optional[Dict[str, Var]]]:
         """If there are observations available, return them as a dictionary."""
-        has_observations = False
-        if self.model is not None:
-            if any((hasattr(obs, "observations") for obs in self.model.observed_RVs)):
-                has_observations = True
-        if has_observations:
-            assert self.model is not None
-            return {obs.name: obs.observations for obs in self.model.observed_RVs}
-        return None
+        if self.model is None:
+            return (None, None)
+        observations = {}
+        multi_observations = {}
+        for obs in self.model.observed_RVs:
+            if hasattr(obs, "observations"):
+                observations[obs.name] = obs.observations
+            elif hasattr(obs, "data"):
+                for key, val in obs.data.items():
+                    multi_observations[key] = val.eval() if hasattr(val, "eval") else val
+        return observations, multi_observations
 
     def split_trace(self) -> Tuple[Union[None, MultiTrace], Union[None, MultiTrace]]:
         """Split MultiTrace object into posterior and warmup.
@@ -361,7 +364,7 @@ class PyMC3Converter:  # pylint: disable=too-many-instance-attributes
             )
         return priors_dict
 
-    @requires("observations")
+    @requires(["observations", "multi_observations"])
     @requires("model")
     def observed_data_to_xarray(self):
         """Convert observed data to xarray."""
@@ -372,7 +375,7 @@ class PyMC3Converter:  # pylint: disable=too-many-instance-attributes
         else:
             dims = self.dims
         observed_data = {}
-        for name, vals in self.observations.items():
+        for name, vals in {**self.observations, **self.multi_observations}.items():
             if hasattr(vals, "get_value"):
                 vals = vals.get_value()
             vals = utils.one_de(vals)
diff --git a/arviz/plots/backends/bokeh/loopitplot.py b/arviz/plots/backends/bokeh/loopitplot.py
index 11a00c8..48f85f1 100644
--- a/arviz/plots/backends/bokeh/loopitplot.py
+++ b/arviz/plots/backends/bokeh/loopitplot.py
@@ -1,10 +1,10 @@
 """Bokeh loopitplot."""
 import bokeh.plotting as bkp
+from bokeh.models import BoxAnnotation
 import numpy as np
 
 from . import backend_kwarg_defaults
 from .. import show_layout
-from ...hdiplot import plot_hdi
 from ...kdeplot import _fast_kde
 
 
@@ -21,8 +21,8 @@ def plot_loo_pit(
     ecdf_fill,
     use_hdi,
     x_vals,
-    unif_densities,
     hdi_kwargs,
+    hdi_odds,
     n_unif,
     unif,
     plot_unif_kwargs,
@@ -43,7 +43,7 @@ def plot_loo_pit(
     if ax is None:
         backend_kwargs.setdefault("width", int(figsize[0] * dpi))
         backend_kwargs.setdefault("height", int(figsize[1] * dpi))
-        ax = bkp.figure(**backend_kwargs)
+        ax = bkp.figure(x_range=(0, 1), **backend_kwargs)
 
     if ecdf:
         if plot_kwargs.get("drawstyle") == "steps-mid":
@@ -115,20 +115,21 @@ def plot_loo_pit(
                 )
     else:
         if use_hdi:
-            plot_hdi(
-                x_vals,
-                unif_densities,
-                backend="bokeh",
-                ax=ax,
-                backend_kwargs={},
-                show=False,
-                **hdi_kwargs
+            ax.add_layout(
+                BoxAnnotation(
+                    bottom=hdi_odds[1],
+                    top=hdi_odds[0],
+                    fill_alpha=hdi_kwargs.pop("alpha"),
+                    fill_color=hdi_kwargs.pop("color"),
+                    **hdi_kwargs
+                )
             )
         else:
             for idx in range(n_unif):
-                unif_density, _, _ = _fast_kde(unif[idx, :], xmin=0, xmax=1)
+                unif_density, xmin, xmax = _fast_kde(unif[idx, :])
+                x_s = np.linspace(xmin, xmax, len(unif_density))
                 ax.line(
-                    x_vals,
+                    x_s,
                     unif_density,
                     line_color=plot_unif_kwargs.get("color", "black"),
                     line_alpha=plot_unif_kwargs.get("alpha", 0.1),
diff --git a/arviz/plots/backends/matplotlib/loopitplot.py b/arviz/plots/backends/matplotlib/loopitplot.py
index d21070f..6a26717 100644
--- a/arviz/plots/backends/matplotlib/loopitplot.py
+++ b/arviz/plots/backends/matplotlib/loopitplot.py
@@ -4,7 +4,6 @@ import numpy as np
 
 from . import backend_kwarg_defaults, backend_show
 from ....numeric_utils import _fast_kde
-from ...hdiplot import plot_hdi
 
 
 def plot_loo_pit(
@@ -20,8 +19,8 @@ def plot_loo_pit(
     ecdf_fill,
     use_hdi,
     x_vals,
-    unif_densities,
     hdi_kwargs,
+    hdi_odds,
     n_unif,
     unif,
     plot_unif_kwargs,
@@ -54,14 +53,20 @@ def plot_loo_pit(
         else:
             ax.plot(unif_ecdf, p975 - unif_ecdf, unif_ecdf, p025 - unif_ecdf, **plot_unif_kwargs)
     else:
+        x_ss = np.empty((n_unif, len(loo_pit_kde)))
+        u_dens = np.empty((n_unif, len(loo_pit_kde)))
         if use_hdi:
-            plot_hdi(x_vals, unif_densities, **hdi_kwargs)
+            ax.axhspan(*hdi_odds, **hdi_kwargs)
         else:
             for idx in range(n_unif):
-                unif_density, _, _ = _fast_kde(unif[idx, :], xmin=0, xmax=1)
-                ax.plot(x_vals, unif_density, **plot_unif_kwargs)
+                unif_density, xmin, xmax = _fast_kde(unif[idx, :])
+                x_s = np.linspace(xmin, xmax, len(unif_density))
+                x_ss[idx] = x_s
+                u_dens[idx] = unif_density
+            ax.plot(x_ss.T, u_dens.T, **plot_unif_kwargs)
         ax.plot(x_vals, loo_pit_kde, **plot_kwargs)
-
+        ax.set_xlim(0, 1)
+        ax.set_ylim(0, None)
     ax.tick_params(labelsize=xt_labelsize)
     if legend:
         if not (use_hdi or (ecdf and ecdf_fill)):
diff --git a/arviz/plots/hdiplot.py b/arviz/plots/hdiplot.py
index ba5be26..f9dbcd5 100644
--- a/arviz/plots/hdiplot.py
+++ b/arviz/plots/hdiplot.py
@@ -33,13 +33,13 @@ def plot_hdi(
     Parameters
     ----------
     x : array-like
-        Values to plot
+        Values to plot.
     y : array-like
-        values from which to compute the hdi. Assumed shape (chain, draw, \*shape).
+        Values from which to compute the hdi. Assumed shape (chain, draw, \*shape).
     hdi_prob : float, optional
         Probability for the highest density interval. Defaults to 0.94.
     color : str
-        Color used for the limits of the hdi and fill. Should be a valid matplotlib color
+        Color used for the limits of the hdi and fill. Should be a valid matplotlib color.
     circular : bool, optional
         Whether to compute the hdi taking into account `x` is a circular variable
         (in the range [-np.pi, np.pi]) or not. Defaults to False (i.e non-circular variables).
@@ -49,11 +49,11 @@ def plot_hdi(
         Defaults to True.
     smooth_kwargs : dict, optional
         Additional keywords modifying the Savitzky-Golay filter. See Scipy's documentation for
-        details
+        details.
     fill_kwargs : dict
         Keywords passed to `fill_between` (use fill_kwargs={'alpha': 0} to disable fill).
     plot_kwargs : dict
-        Keywords passed to hdi limits
+        Keywords passed to hdi limits.
     ax: axes, optional
         Matplotlib axes or bokeh figures.
     backend: str, optional
@@ -64,7 +64,7 @@ def plot_hdi(
     show : bool, optional
         Call backend show function.
     credible_interval: float, optional
-        deprecated: Please see hdi_prob
+        Deprecated: Please see hdi_prob
 
     Returns
     -------
diff --git a/arviz/plots/kdeplot.py b/arviz/plots/kdeplot.py
index 9e796d9..99f0b07 100644
--- a/arviz/plots/kdeplot.py
+++ b/arviz/plots/kdeplot.py
@@ -182,7 +182,10 @@ def plot_kde(
             "or plot_pair instead of plot_kde"
         )
     if isinstance(values, InferenceData):
-        raise ValueError(" Inference Data object detected. Use plot_posterior instead of plot_kde")
+        raise ValueError(
+            " Inference Data object detected. Use plot_posterior "
+            "or plot_pair instead of plot_kde"
+        )
 
     if values2 is None:
         density, lower, upper = _fast_kde(values, cumulative, bw)
diff --git a/arviz/plots/loopitplot.py b/arviz/plots/loopitplot.py
index e4cff48..f5f1995 100644
--- a/arviz/plots/loopitplot.py
+++ b/arviz/plots/loopitplot.py
@@ -65,9 +65,9 @@ def plot_loo_pit(
     n_unif : int, optional
         Number of datasets to simulate and overlay from the uniform distribution.
     use_hdi : bool, optional
-        Use plot_hdi to fill between hdi values instead of overlaying the uniform distributions.
+        Compute expected hdi values instead of overlaying the sampled uniform distributions.
     credible_interval : float, optional
-        Credible interval of the hdi or of the ECDF theoretical credible interval
+        Theoretical credible interval. Works with ``use_hdi=True`` or ``ecdf=True``.
     figsize : figure size tuple, optional
         If None, size is (8 + numvars, 8 + numvars)
     textsize: int, optional
@@ -86,7 +86,7 @@ def plot_loo_pit(
         Additional keywords passed to ax.plot for overlaid uniform distributions or
         for beta credible interval lines if ``ecdf=True``
     hdi_kwargs : dict, optional
-        Additional keywords passed to az.plot_hdi
+        Additional keywords passed to ax.axhspan
     fill_kwargs : dict, optional
         Additional kwargs passed to ax.fill_between
     backend: str, optional
@@ -168,8 +168,8 @@ def plot_loo_pit(
     p975 = None
     p025 = None
     loo_pit_kde = None
+    hdi_odds = None
     unif = None
-    unif_densities = None
     x_vals = None
 
     if credible_interval is None:
@@ -206,20 +206,19 @@ def plot_loo_pit(
             )
             fill_kwargs.setdefault("label", "{:.3g}% credible interval".format(credible_interval))
     else:
-        loo_pit_kde, _, _ = _fast_kde(loo_pit, xmin=0, xmax=1)
+        loo_pit_kde, xmin, xmax = _fast_kde(loo_pit)
 
         unif = np.random.uniform(size=(n_unif, loo_pit.size))
-        x_vals = np.linspace(0, 1, len(loo_pit_kde))
+        x_vals = np.linspace(xmin, xmax, len(loo_pit_kde))
         if use_hdi:
+            n_obs = loo_pit.size
+            hdi_ = stats.beta(n_obs / 2, n_obs / 2).ppf((1 - credible_interval) / 2)
+            hdi_odds = (hdi_ / (1 - hdi_), (1 - hdi_) / hdi_)
             if hdi_kwargs is None:
                 hdi_kwargs = {}
             hdi_kwargs.setdefault("color", to_hex(hsv_to_rgb(light_color)))
-            hdi_fill_kwargs = hdi_kwargs.pop("fill_kwargs", {})
-            hdi_fill_kwargs.setdefault("label", "Uniform hdi")
-            hdi_kwargs["fill_kwargs"] = hdi_fill_kwargs
-            hdi_kwargs["credible_interval"] = credible_interval
-
-            unif_densities = np.empty((n_unif, len(loo_pit_kde)))
+            hdi_kwargs.setdefault("alpha", 0.35)
+            hdi_kwargs.setdefault("label", "Uniform hdi")
 
     loo_pit_kwargs = dict(
         ax=ax,
@@ -234,8 +233,8 @@ def plot_loo_pit(
         ecdf_fill=ecdf_fill,
         use_hdi=use_hdi,
         x_vals=x_vals,
-        unif_densities=unif_densities,
         hdi_kwargs=hdi_kwargs,
+        hdi_odds=hdi_odds,
         n_unif=n_unif,
         unif=unif,
         plot_unif_kwargs=plot_unif_kwargs,
@@ -253,14 +252,8 @@ def plot_loo_pit(
     backend = backend.lower()
 
     if backend == "bokeh":
-
-        if (
-            loo_pit_kwargs["hdi_kwargs"] is not None
-            and "fill_kwargs" in loo_pit_kwargs["hdi_kwargs"]
-            and loo_pit_kwargs["hdi_kwargs"]["fill_kwargs"] is not None
-            and "label" in loo_pit_kwargs["hdi_kwargs"]["fill_kwargs"]
-        ):
-            loo_pit_kwargs["hdi_kwargs"]["fill_kwargs"].pop("label")
+        if use_hdi:
+            loo_pit_kwargs["hdi_kwargs"].pop("label", None)
         loo_pit_kwargs.pop("legend")
         loo_pit_kwargs.pop("xt_labelsize")
         loo_pit_kwargs.pop("credible_interval")
diff --git a/elections/ArviZ_2020.md b/elections/ArviZ_2020.md
new file mode 100644
index 0000000..1342855
--- /dev/null
+++ b/elections/ArviZ_2020.md
@@ -0,0 +1,58 @@
+# Election 2020
+
+## Summary of Process
+This is the first election cycle for ArviZ. During this cycle we are aiming
+for the following outcomes
+
+* Ratification of GOVERNANCE.md
+* Identification and listing of Core Contributors
+* Nominations for Council Candidates
+* Council Elections. Particularly
+  * Size of Council
+  * Inaugural ArviZ Council
+
+This being the first election means that the process will have more steps than
+the ones listed in GOVERNANCE.md.
+
+The moderators for these tasks live in the PST and CST timezone so the dates are reflective of their localities.
+
+## Process Details
+### Identification of Core Contributors (June 7th)
+1. A Google Form will be shared over Slack to get core contributor names, emails, and optionally Github username.
+    * Emails are needed for Elections steps so NumFOCUS can mail and track votes
+    * The Google Form is moderated by Ravin (@canyon289)
+2. Ravin will open a Pull Request with all names and update the PR periodically until June 12th 8pm PST, which is also the cutoff time for the form.
+    * On June 12th 8pm PST Ravin will make the form's responses public as well, with exception of email
+3. The pull request will stay open 24 hours at a minimum. If there is no dissent the pull request will be merged. If there is discussion the timeline below will be shifted.
+
+Note: This is a one time process for identifying core contributors. After this cycle the core contributor acceptance process will performed by the ArviZ Council once elected
+
+### Nominations for Council (June 14th)
+1. A github issue labeled *Arviz 2020 Council Elections* will be opened for nominations
+2. Core contributors can nominate themselves or other core contributors.
+3. Core Contributor must accept acknowledgement with thumbs up on nomination
+   * Without an acknowledgement, they will not be a candidate in the election.
+   * A nomination can be explicitly declined as well in a comment, if that individual
+   feels they will not be able to serve as a Council Member for any reason.
+4. Nominations end at June 20th anywhere on Earth. On June 21st nominations will be compiled in the last comment and the issue will be closed.
+
+
+### First Round Election for Council and Council size (Week of June 21)
+The first election will be for council and council size. Elections will take place over
+a Google Form owned by NumFOCUS. The tallying will be performed in accordance with `GOVERNANCE.md`
+
+The election period will be exactly 7 days from when NumFOCUS email timestamp for the Google Form.
+
+1. NumFOCUS will email all registered voters a ballot for a vote on
+    * Council Size
+    * Council Members
+2. At conclusion of election period NumFOCUS will remove the voters identifying information and share the results (most likely in Google Sheets)
+    * A table of results will be added to this document
+
+### Tie Votes (1 week)
+In the event of a tie there will be a run-off election. The specific timing will depend on the results of the election above.
+The tallying will be performed in accordance with `GOVERNANCE.md`
+
+1. NumFOCUS will email all registered voters a ballot for a vote on tied candidates
+2. At conclusion of election period NumFOCUS will remove the voters identifying information and share the results (most likely in Google Sheets)
+    * A table of results will be added to this document
diff --git a/elections/README.md b/elections/README.md
new file mode 100644
index 0000000..eea9049
--- /dev/null
+++ b/elections/README.md
@@ -0,0 +1,2 @@
+# Elections
+Repository for ArviZ elections


Test patch:
diff --git a/arviz/tests/base_tests/test_plots_bokeh.py b/arviz/tests/base_tests/test_plots_bokeh.py
index 7eb96eb..a853c23 100644
--- a/arviz/tests/base_tests/test_plots_bokeh.py
+++ b/arviz/tests/base_tests/test_plots_bokeh.py
@@ -603,8 +603,8 @@ def test_plot_khat_bad_input(models):
         {},
         {"n_unif": 50},
         {"use_hdi": True, "color": "gray"},
-        {"use_hdi": True, "credible_interval": 0.68, "plot_kwargs": {"alpha": 0.9}},
-        {"use_hdi": True, "hdi_kwargs": {"smooth": False}},
+        {"use_hdi": True, "credible_interval": 0.68},
+        {"use_hdi": True, "hdi_kwargs": {"line_dash": "dashed", "alpha": 0}},
         {"ecdf": True},
         {"ecdf": True, "ecdf_fill": False, "plot_unif_kwargs": {"line_dash": "--"}},
         {"ecdf": True, "credible_interval": 0.97, "fill_kwargs": {"color": "red"}},
diff --git a/arviz/tests/base_tests/test_plots_matplotlib.py b/arviz/tests/base_tests/test_plots_matplotlib.py
index 4e2a466..9a157ec 100644
--- a/arviz/tests/base_tests/test_plots_matplotlib.py
+++ b/arviz/tests/base_tests/test_plots_matplotlib.py
@@ -1101,8 +1101,8 @@ def test_plot_ess_no_divergences(models):
         {},
         {"n_unif": 50, "legend": False},
         {"use_hdi": True, "color": "gray"},
-        {"use_hdi": True, "credible_interval": 0.68, "plot_kwargs": {"ls": "--"}},
-        {"use_hdi": True, "hdi_kwargs": {"smooth": False}},
+        {"use_hdi": True, "credible_interval": 0.68},
+        {"use_hdi": True, "hdi_kwargs": {"fill": 0.1}},
         {"ecdf": True},
         {"ecdf": True, "ecdf_fill": False, "plot_unif_kwargs": {"ls": "--"}},
         {"ecdf": True, "credible_interval": 0.97, "fill_kwargs": {"hatch": "/"}},
diff --git a/arviz/tests/external_tests/test_data_pymc.py b/arviz/tests/external_tests/test_data_pymc.py
index 8abfba5..cf8e093 100644
--- a/arviz/tests/external_tests/test_data_pymc.py
+++ b/arviz/tests/external_tests/test_data_pymc.py
@@ -227,7 +227,7 @@ class TestDataPyMC3:
             "posterior": ["x"],
             "observed_data": ["y1", "y2"],
             "log_likelihood": ["y1", "y2"],
-            "sample_stats": ["diverging", "lp"],
+            "sample_stats": ["diverging", "lp", "~log_likelihood"],
         }
         if not log_likelihood:
             test_dict.pop("log_likelihood")
@@ -237,7 +237,6 @@ class TestDataPyMC3:
 
         fails = check_multiple_attrs(test_dict, inference_data)
         assert not fails
-        assert not hasattr(inference_data.sample_stats, "log_likelihood")
 
     @pytest.mark.skipif(
         version_info < (3, 6), reason="Requires updated PyMC3, which needs Python 3.6"
@@ -250,11 +249,48 @@ class TestDataPyMC3:
             )
             trace = pm.sample(100, chains=2)
             inference_data = from_pymc3(trace=trace)
-        assert inference_data
-        assert not hasattr(inference_data, "observed_data")
-        assert hasattr(inference_data, "posterior")
-        assert hasattr(inference_data, "sample_stats")
-        assert hasattr(inference_data, "log_likelihood")
+        test_dict = {
+            "posterior": ["mu"],
+            "sample_stats": ["lp"],
+            "log_likelihood": ["x"],
+            "observed_data": ["value", "~x"],
+        }
+        fails = check_multiple_attrs(test_dict, inference_data)
+        assert not fails
+        assert inference_data.observed_data.value.dtype.kind == "f"
+
+    def test_multiobservedrv_to_observed_data(self):
+        # fake regression data, with weights (W)
+        np.random.seed(2019)
+        N = 100
+        X = np.random.uniform(size=N)
+        W = 1 + np.random.poisson(size=N)
+        a, b = 5, 17
+        Y = a + np.random.normal(b * X)
+
+        with pm.Model():
+            a = pm.Normal("a", 0, 10)
+            b = pm.Normal("b", 0, 10)
+            mu = a + b * X
+            sigma = pm.HalfNormal("sigma", 1)
+
+            def weighted_normal(y, w):
+                return w * pm.Normal.dist(mu=mu, sd=sigma).logp(y)
+
+            y_logp = pm.DensityDist(  # pylint: disable=unused-variable
+                "y_logp", weighted_normal, observed={"y": Y, "w": W}
+            )
+            trace = pm.sample(20, tune=20)
+            idata = from_pymc3(trace)
+        test_dict = {
+            "posterior": ["a", "b", "sigma"],
+            "sample_stats": ["lp"],
+            "log_likelihood": ["y_logp"],
+            "observed_data": ["y", "w"],
+        }
+        fails = check_multiple_attrs(test_dict, idata)
+        assert not fails
+        assert idata.observed_data.y.dtype.kind == "f"
 
     def test_single_observation(self):
         with pm.Model():