Repository: autogluon/autogluon

Issue:
Balanced accuracy error
(this is with 6b247bf)

Consider (from `tabular-indepth` tutorial):

```python
>>> y_test[:5]
0               Sales
1               Sales
2     Exec-managerial
3     Exec-managerial
4      Prof-specialty
Name: occupation, dtype: object

>>> y_pred[:5]
array([' Other-service', ' Craft-repair', ' Exec-managerial', ' Sales',
       ' Other-service'], dtype=object)
```

**with ag**

```python
>>> ag.utils.tabular.metrics.accuracy(y_test, y_pred)
0.3393387245368001

>>> ag.utils.tabular.metrics.balanced_accuracy(y_test, y_pred)
[error see stacktrace]
```

**with sklearn**

```python
>>> sklearn.metrics.balanced_accuracy_score(y_test, y_pred)
0.21896145445995055
```

### Reason

I believe the issue stems from this line: https://github.com/awslabs/autogluon/blob/6b247bfea9d504381cc512e36ba1909e6c54c0c3/autogluon/utils/tabular/metrics/classification_metrics.py#L21 

which would be ok if `prediction` and `solution` have been encoded to integer values but not in general. The max should be over the number of unique elements. This requires a bit more than a one line refactoring though, I'll open a PR. 

### Stacktrace

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-28-ddcca1d23e69> in <module>
----> 1 ag.utils.tabular.metrics.balanced_accuracy(y_test, y_pred)

~/Desktop/github-aws/autogluon-public/autogluon/utils/tabular/metrics/__init__.py in __call__(self, y_true, y_pred, sample_weight)
     87         else:
     88             return self._sign * self._score_func(y_true, y_pred,
---> 89                                                  **self._kwargs)
     90 
     91 

~/Desktop/github-aws/autogluon-public/autogluon/utils/tabular/metrics/classification_metrics.py in balanced_accuracy(solution, prediction)
     19     elif y_type == 'multiclass':
     20         # Need to create a multiclass solution and a multiclass predictions
---> 21         max_class = int(np.max((np.max(solution), np.max(prediction))))
     22         solution_binary = np.zeros((len(solution), max_class + 1))
     23         prediction_binary = np.zeros((len(prediction), max_class + 1))

<__array_function__ internals> in amax(*args, **kwargs)

~/Desktop/github-aws/ghaws/lib/python3.7/site-packages/numpy/core/fromnumeric.py in amax(a, axis, out, keepdims, initial, where)
   2666     """
   2667     return _wrapreduction(a, np.maximum, 'max', axis, None, out,
-> 2668                           keepdims=keepdims, initial=initial, where=where)
   2669 
   2670 

~/Desktop/github-aws/ghaws/lib/python3.7/site-packages/numpy/core/fromnumeric.py in _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs)
     88                 return reduction(axis=axis, out=out, **passkwargs)
     89 
---> 90     return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
     91 
     92 

TypeError: cannot perform reduce with flexible type
```


Discussion:

Resolving patch:
diff --git a/autogluon/utils/tabular/metrics/classification_metrics.py b/autogluon/utils/tabular/metrics/classification_metrics.py
--- a/autogluon/utils/tabular/metrics/classification_metrics.py
+++ b/autogluon/utils/tabular/metrics/classification_metrics.py
@@ -17,15 +17,19 @@ def balanced_accuracy(solution, prediction):
         pass
 
     elif y_type == 'multiclass':
-        # Need to create a multiclass solution and a multiclass predictions
-        max_class = int(np.max((np.max(solution), np.max(prediction))))
-        solution_binary = np.zeros((len(solution), max_class + 1))
-        prediction_binary = np.zeros((len(prediction), max_class + 1))
-        for i in range(len(solution)):
-            solution_binary[i, int(solution[i])] = 1
-            prediction_binary[i, int(prediction[i])] = 1
-        solution = solution_binary
-        prediction = prediction_binary
+        n = len(solution)
+        unique_sol, encoded_sol = np.unique(solution, return_inverse=True)
+        unique_pred, encoded_pred = np.unique(prediction, return_inverse=True)
+        classes = np.unique(np.concatenate((unique_sol, unique_pred)))
+        map_sol = np.array([np.where(classes==c)[0][0] for c in unique_sol])
+        map_pred = np.array([np.where(classes==c)[0][0] for c in unique_pred])
+        # one hot encoding
+        sol_ohe = np.zeros((n, len(classes)))
+        pred_ohe = np.zeros((n, len(classes)))
+        sol_ohe[np.arange(n), map_sol[encoded_sol]] = 1
+        pred_ohe[np.arange(n), map_pred[encoded_pred]] = 1
+        solution = sol_ohe
+        prediction = pred_ohe
 
     elif y_type == 'multilabel-indicator':
         solution = solution.toarray()


Test patch: