Repository: statsmodels/statsmodels

Issue:
Specify constant column in add_constant(has_constant='raise') when it already exists
#### Is your feature request related to a problem? Please describe
I called `add_constant` to a dataset which had an existing constant, but I didn't know which column it was.

#### Describe the solution you'd like
Mention the constant column identified when throwing the error in `add_constant(has_constant='raise')`.

#### Describe alternatives you have considered
One could find it themselves by looping through columns and taking the variance.

#### Additional context
The current error is:
```
ValueError: x contains a constant. Adding a constant with trend='c' is not allowed.
```

When X is an array or something other than a DataFrame, the error could specify the column number instead.


Discussion:
Here's the relevant code:
https://github.com/statsmodels/statsmodels/blob/38a0791aa767b1a804c2ed1a9777cf9d4ea93b65/statsmodels/tools/tools.py#L325-L331

I think this could be done by replacing the last line with:
```
raise ValueError("data already contains a constant:" + " and ".join(x.columns[is_nonzero_const]))
```

Resolving patch:
diff --git a/statsmodels/tools/tools.py b/statsmodels/tools/tools.py
--- a/statsmodels/tools/tools.py
+++ b/statsmodels/tools/tools.py
@@ -317,7 +317,8 @@ def add_constant(data, prepend=True, has_constant='skip'):
 
     # Special case for NumPy
     x = np.asanyarray(data)
-    if x.ndim == 1:
+    ndim = x.ndim
+    if ndim == 1:
         x = x[:, None]
     elif x.ndim > 2:
         raise ValueError('Only implemented for 2-dimensional arrays')
@@ -328,7 +329,12 @@ def add_constant(data, prepend=True, has_constant='skip'):
         if has_constant == 'skip':
             return x
         elif has_constant == 'raise':
-            raise ValueError("data already contains a constant")
+            if ndim == 1:
+                raise ValueError("data is constant.")
+            else:
+                columns = np.arange(x.shape[1])
+                cols = ",".join([str(c) for c in columns[is_nonzero_const]])
+                raise ValueError(f"Column(s) {cols} are constant.")
 
     x = [np.ones(x.shape[0]), x]
     x = x if prepend else x[::-1]
diff --git a/statsmodels/tsa/tsatools.py b/statsmodels/tsa/tsatools.py
--- a/statsmodels/tsa/tsatools.py
+++ b/statsmodels/tsa/tsatools.py
@@ -116,8 +116,20 @@ def safe_is_const(s):
 
         if np.any(col_const):
             if has_constant == 'raise':
-                msg = "x contains a constant. Adding a constant with " \
-                      "trend='{0}' is not allowed.".format(trend)
+                if x.ndim == 1:
+                    base_err = "x is constant."
+                else:
+                    columns = np.arange(x.shape[1])[col_const]
+                    if isinstance(x, pd.DataFrame):
+                        columns = x.columns
+                    const_cols = ", ".join([str(c) for c in columns])
+                    base_err = (
+                        "x contains one or more constant columns. Column(s) "
+                        f"{const_cols} are constant."
+                    )
+                msg = (
+                    f"{base_err} Adding a constant with trend='{trend}' is not allowed."
+                )
                 raise ValueError(msg)
             elif has_constant == 'skip':
                 columns = columns[1:]


Test patch:
diff --git a/statsmodels/tsa/tests/test_arima.py b/statsmodels/tsa/tests/test_arima.py
--- a/statsmodels/tsa/tests/test_arima.py
+++ b/statsmodels/tsa/tests/test_arima.py
@@ -2580,7 +2580,7 @@ def test_constant_column_trend():
     model = ARIMA(endog=endog, order=(1, 1, 0), exog=exog)
 
     # Fitting with a constant and constant exog raises because of colinearity
-    with pytest.raises(ValueError, match="x contains a constant"):
+    with pytest.raises(ValueError, match="x contains one or more constant"):
         model.fit(trend="c", disp=-1)
 
     # FIXME: calling model.fit(trend="nc") raises for orthogonal reasons