Calculate the widely available information criterion.

    Also calculates the WAIC's standard error and the effective number of
    parameters of the samples in trace from model. Read more theory here - in
    a paper by some of the leading authorities on model selection
    dx.doi.org/10.1111/1467-9868.00353

    Parameters
    ----------
    data : obj
        Any object that can be converted to an az.InferenceData object
        Refer to documentation of az.convert_to_dataset for details
    pointwise: bool
        if True the pointwise predictive accuracy will be returned.
        Default False
    scale : str
        Output scale for loo. Available options are:

        - `deviance` : (default) -2 * (log-score)
        - `log` : 1 * log-score
        - `negative_log` : -1 * (log-score)

    Returns
    -------
    DataFrame with the following columns:
    waic: widely available information criterion
    waic_se: standard error of waic
    p_waic: effective number parameters
    var_warn: 1 if posterior variance of the log predictive
         densities exceeds 0.4
    waic_i: and array of the pointwise predictive accuracy, only if pointwise True
    waic_scale: scale of the waic results

def waic(data, pointwise=False, scale="deviance"):
    """Calculate the widely available information criterion.

    Also calculates the WAIC's standard error and the effective number of
    parameters of the samples in trace from model. Read more theory here - in
    a paper by some of the leading authorities on model selection
    dx.doi.org/10.1111/1467-9868.00353

    Parameters
    ----------
    data : obj
        Any object that can be converted to an az.InferenceData object
        Refer to documentation of az.convert_to_dataset for details
    pointwise: bool
        if True the pointwise predictive accuracy will be returned.
        Default False
    scale : str
        Output scale for loo. Available options are:

        - `deviance` : (default) -2 * (log-score)
        - `log` : 1 * log-score
        - `negative_log` : -1 * (log-score)

    Returns
    -------
    DataFrame with the following columns:
    waic: widely available information criterion
    waic_se: standard error of waic
    p_waic: effective number parameters
    var_warn: 1 if posterior variance of the log predictive
         densities exceeds 0.4
    waic_i: and array of the pointwise predictive accuracy, only if pointwise True
    waic_scale: scale of the waic results
    """
    inference_data = convert_to_inference_data(data)
    for group in ("sample_stats",):
        if not hasattr(inference_data, group):
            raise TypeError(
                "Must be able to extract a {group} group from data!".format(group=group)
            )
    if "log_likelihood" not in inference_data.sample_stats:
        raise TypeError("Data must include log_likelihood in sample_stats")
    log_likelihood = inference_data.sample_stats.log_likelihood

    if scale.lower() == "deviance":
        scale_value = -2
    elif scale.lower() == "log":
        scale_value = 1
    elif scale.lower() == "negative_log":
        scale_value = -1
    else:
        raise TypeError('Valid scale values are "deviance", "log", "negative_log"')

    n_samples = log_likelihood.chain.size * log_likelihood.draw.size
    new_shape = (n_samples,) + log_likelihood.shape[2:]
    log_likelihood = log_likelihood.values.reshape(*new_shape)

    lppd_i = _logsumexp(log_likelihood, axis=0, b_inv=log_likelihood.shape[0])

    vars_lpd = np.var(log_likelihood, axis=0)
    warn_mg = 0
    if np.any(vars_lpd > 0.4):
        warnings.warn(
            """For one or more samples the posterior variance of the log predictive
        densities exceeds 0.4. This could be indication of WAIC starting to fail see
        http://arxiv.org/abs/1507.04544 for details
        """
        )
        warn_mg = 1

    waic_i = scale_value * (lppd_i - vars_lpd)
    waic_se = (len(waic_i) * np.var(waic_i)) ** 0.5
    waic_sum = np.sum(waic_i)
    p_waic = np.sum(vars_lpd)

    if pointwise:
        if np.equal(waic_sum, waic_i).all():  # pylint: disable=no-member
            warnings.warn(
                """The point-wise WAIC is the same with the sum WAIC, please double check
            the Observed RV in your model to make sure it returns element-wise logp.
            """
            )
        return pd.Series(
            data=[waic_sum, waic_se, p_waic, warn_mg, waic_i, scale],
            index=["waic", "waic_se", "p_waic", "warning", "waic_i", "waic_scale"],
        )
    else:
        return pd.Series(
            data=[waic_sum, waic_se, p_waic, warn_mg, scale],
            index=["waic", "waic_se", "p_waic", "warning", "waic_scale"],
        )