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. 


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/arviz-devs/arviz/pull/2413?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arviz-devs) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 86.75%. Comparing base [(`0fc1117`)](https://app.codecov.io/gh/arviz-devs/arviz/commit/0fc11178e3802de9e2e6557ce455cced9a22974f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arviz-devs) to head [(`3852b86`)](https://app.codecov.io/gh/arviz-devs/arviz/commit/3852b86ffd401abc01b45ee63d163e6f5d8886ab?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arviz-devs).

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##             main    #2413       +/-   ##
===========================================
+ Coverage   68.26%   86.75%   +18.48%     
===========================================
  Files         124      124               
  Lines       12936    12938        +2     
===========================================
+ Hits         8831    11224     +2393     
+ Misses       4105     1714     -2391     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/arviz-devs/arviz/pull/2413?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arviz-devs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=arviz-devs).

amaloney: thansk @milesalanmoore! I will try to take a look at this later this week
milesalanmoore: Thank you, @amaloney, for your time and very thoughtful message (https://github.com/arviz-devs/arviz/issues/2412#issuecomment-2611669309). I have modified the error type and message per your suggestions! Let me know what you think. I took a bit of a liberty in the error message and add a recommendation to use az.plot_forest -- but I am happy to remove this or further modify the error message. 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4798a43..687b1db 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 196a1f5..5462816 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 2af7a71..87685ff 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 9706ac9..20d305a 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -192,9 +192,9 @@ html_theme_options = {
             "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 c2646a7..fcc548e 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 @@ from ...plots import (
     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)