Repository: arviz-devs/arviz

Issue:
Categorical x apparently not supported by arviz.plot_hdi()
Hello arviz-devs! Thanks for a wonderful package. This issue comes from a [discussion on the PyMC discourse](https://discourse.pymc.io/t/unexpected-behavior-with-arviz-plot-hdi-with-categorical-x/16403) including @tomicapretto and I. 

**Describe the bug**
To my understanding, [plot_hdi()](https://github.com/arviz-devs/arviz/blob/main/arviz/plots/hdiplot.py) does not currently support categorical x values (see code below), but this isn't explicitly noted in the documentation nor is there a ValueError or TypeError raised. Further, the functions default of `smooth=True` throws it's own error when a user passes a categorical x.

**To Reproduce**
```Python
import arviz as az
import bambi as bmb
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Simulate data
np.random.seed(42)
x = ['A', 'B', 'C']
yA = np.random.normal(loc=5, scale=3, size=30)
yB = np.random.normal(loc=2, scale=4, size=30)
yC = np.random.normal(loc=7, scale=1.8, size=30)

# Create a DataFrame
data = pd.DataFrame({
    'y': np.concatenate([yA, yB, yC]),
    'group': np.repeat(x, repeats=30)
})
data['group'] = data['group'].astype('category')

# Plot the data
plt.figure(figsize=(8, 6))
sns.boxplot(x='group', y='y', data=data, palette='Set3')
sns.stripplot(x='group', y='y', data=data, color='black', alpha=0.5, jitter=True)
plt.title('Distribution of y across groups')
plt.xlabel('Group')
plt.ylabel('y')
plt.show()

```

![Image](https://github.com/user-attachments/assets/5474a535-4523-4263-9cbe-3d99349e55a3)

```Python
# Fit a Bayesian ANOVA model using Bambi
model = bmb.Model('y ~ group', data)
idata = model.fit()

preds = model.predict(idata, kind="response_params", inplace=False)
y_mu = az.extract(preds["posterior"])["mu"].values
group = data.loc[:, "group"].values

az.plot_hdi(x=group, y=y_mu.T)

```
Returns: 

> #UFuncTypeError: ufunc 'multiply' did not contain a loop with signature matching types (dtype('<U1'), dtype('float64')) -> None

The traceback points to `np.linspace` under the `if smooth:` block:
https://github.com/arviz-devs/arviz/blob/0fc11178e3802de9e2e6557ce455cced9a22974f/arviz/plots/hdiplot.py#L171-L182

Setting smooth to **False** does not return an expected plot:
```Python
az.plot_hdi(x=group, y=y_mu.T, smooth=False)
```

![Image](https://github.com/user-attachments/assets/b6441c4d-05f2-4ef2-bbfd-f3db3df536ee)

**Expected behavior**
Given the documentation, I'd expect the behavior to mirror the output from `bambi.interpret`

```Python
bmb.interpret.plot_predictions(
    model=model,
    idata=idata,
    conditional="group",
);
```

![Image](https://github.com/user-attachments/assets/e598f354-3425-482e-bf4c-11479afb6314)

**Additional context**
arviz 0.20.0 via conda-forge
Python 3.12.0

---

I think a TypeError informing the user of the lack of support for categorical (str) types would be very helpful to future users.

Hints from issue comments:
@amaloney: Thanks @milesalanmoore I was able to reproduce everything you gave above, and I thank you greatly for the effort given to the minimum reproducible example.

It looks like you were expecting categorical data to plot similarly to how bambi plots HDI values for categories. What I find interesting is that the ArviZ version is actually returning the same data, but it is connecting all the points together into lines. You can see this by overlaying the bambi plot with the ArviZ one.

I think a better error message at this time would be a `NotImplementedError` with a message saying `plot_hdi` does not plot categorical values.

On another note, it looks to me like you are wanting a forest plot. To do that using ArviZ, I would do the following.

```python
import arviz as az
import bambi as bmb
import numpy as np
import pandas as pd
import xarray as xr

# Simulate data
np.random.seed(42)
x = ['A', 'B', 'C']
yA = np.random.normal(loc=5, scale=3, size=30)
yB = np.random.normal(loc=2, scale=4, size=30)
yC = np.random.normal(loc=7, scale=1.8, size=30)

# Create a DataFrame
data = pd.DataFrame({
    'y': np.concatenate([yA, yB, yC]),
    'group': np.repeat(x, repeats=30)
})

# Fit a Bayesian ANOVA model using Bambi
model = bmb.Model('y ~ group', data)
idata = model.fit()
preds = model.predict(idata, kind="response_params", inplace=False)
y_mu = az.extract(preds["posterior"])["mu"].values

# forest plot
az.plot_forest(
    xr.DataArray(
        data=y_mu.reshape(90, 1, 4000),
        dims=["group", "chain", "draw"],
        coords={"group": data["group"].values},
    )
)
plt.show()
```

![Image](https://github.com/user-attachments/assets/752333fb-d1ac-462d-8bb9-72d8fbee6469)

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4798a43ec7..687b1db477 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 - Only emit a warning for custom groups in `InferenceData` when explicitly requested ([2401](https://github.com/arviz-devs/arviz/pull/2401))
 - Splits Bayes Factor computation out from `az.plot_bf` into `az.bayes_factor` ([2402](https://github.com/arviz-devs/arviz/issues/2402))
 - Update `method="sd"` of `mcse` to not use normality assumption ([2167](https://github.com/arviz-devs/arviz/pull/2167)) 
+- Add exception in `az.plot_hdi` for `x` of type `str` ([2413](https://github.com/arviz-devs/arviz/pull/2413))
 
 ### Documentation
 - Add example of ECDF comparison plot to gallery ([2178](https://github.com/arviz-devs/arviz/pull/2178))
diff --git a/arviz/plots/hdiplot.py b/arviz/plots/hdiplot.py
index 196a1f53d9..546281634f 100644
--- a/arviz/plots/hdiplot.py
+++ b/arviz/plots/hdiplot.py
@@ -136,6 +136,11 @@ def plot_hdi(
     x = np.asarray(x)
     x_shape = x.shape
 
+    if isinstance(x[0], str):
+        raise NotImplementedError(
+            "The `arviz.plot_hdi()` function does not support categorical data. "
+            "Consider using `arviz.plot_forest()`."
+        )
     if y is None and hdi_data is None:
         raise ValueError("One of {y, hdi_data} is required")
     if hdi_data is not None and y is not None:
diff --git a/doc/source/community.md b/doc/source/community.md
index 2af7a7193d..87685ffd6b 100644
--- a/doc/source/community.md
+++ b/doc/source/community.md
@@ -40,8 +40,8 @@ between programming languages and encourage collaboration.
 ## Participate online
 There are many alternatives and channels available to interact with other ArviZ users.
 
-### Twitter
-To begin with, you may want to follow us on Twitter! We are [@arviz_devs](https://twitter.com/arviz_devs).
+### Bluesky
+To begin with, you may want to follow us on Bluesky! We are [@arviz.bsky.social](https://bsky.app/profile/arviz.bsky.social).
 It is the best way to be up to date with the latest developments and events related
 to ArviZ.
 
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 9706ac9805..20d305a3ed 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -192,9 +192,9 @@
             "icon": "fa-brands fa-github",
         },
         {
-            "name": "Twitter",
-            "url": "https://twitter.com/arviz_devs",
-            "icon": "fa-brands fa-twitter",
+            "name": "Bluesky",
+            "url": "https://bsky.app/profile/arviz.bsky.social",
+            "icon": "fa-brands fa-bluesky",
         },
         {
             "name": "Mastodon",


Test patch:
diff --git a/arviz/tests/base_tests/test_plots_matplotlib.py b/arviz/tests/base_tests/test_plots_matplotlib.py
index c2646a73f1..fcc548e9e7 100644
--- a/arviz/tests/base_tests/test_plots_matplotlib.py
+++ b/arviz/tests/base_tests/test_plots_matplotlib.py
@@ -2,21 +2,22 @@
 
 # pylint: disable=redefined-outer-name,too-many-lines
 import os
+import re
 from copy import deepcopy
 
 import matplotlib.pyplot as plt
 import numpy as np
 import pytest
+import xarray as xr
 from matplotlib import animation
 from pandas import DataFrame
 from scipy.stats import gaussian_kde, norm
-import xarray as xr
 
 from ...data import from_dict, load_arviz_data
 from ...plots import (
     plot_autocorr,
-    plot_bpv,
     plot_bf,
+    plot_bpv,
     plot_compare,
     plot_density,
     plot_dist,
@@ -43,20 +44,20 @@
     plot_ts,
     plot_violin,
 )
+from ...plots.dotplot import wilkinson_algorithm
+from ...plots.plot_utils import plot_point_interval
 from ...rcparams import rc_context, rcParams
 from ...stats import compare, hdi, loo, waic
 from ...stats.density_utils import kde as _kde
-from ...utils import _cov, BehaviourChangeWarning
-from ...plots.plot_utils import plot_point_interval
-from ...plots.dotplot import wilkinson_algorithm
+from ...utils import BehaviourChangeWarning, _cov
 from ..helpers import (  # pylint: disable=unused-import
+    RandomVariableTestClass,
     create_model,
     create_multidimensional_model,
     does_not_warn,
     eight_schools_params,
     models,
     multidim_models,
-    RandomVariableTestClass,
 )
 
 rcParams["data.load"] = "eager"
@@ -1236,6 +1237,23 @@ def test_plot_hdi_dataset_error(models):
         plot_hdi(np.arange(8), hdi_data=hdi_data)
 
 
+def test_plot_hdi_string_error():
+    """Check x as type string raises an error."""
+    x_data = ["a", "b", "c", "d"]
+    y_data = np.random.normal(0, 5, (1, 200, len(x_data)))
+    hdi_data = hdi(y_data)
+    with pytest.raises(
+        NotImplementedError,
+        match=re.escape(
+            (
+                "The `arviz.plot_hdi()` function does not support categorical data. "
+                "Consider using `arviz.plot_forest()`."
+            )
+        ),
+    ):
+        plot_hdi(x=x_data, y=y_data, hdi_data=hdi_data)
+
+
 def test_plot_hdi_datetime_error():
     """Check x as datetime raises an error."""
     x_data = np.arange(start="2022-01-01", stop="2022-03-01", dtype=np.datetime64)