How to measure if a categorical distribution is concentrated in a very few bins I'm trying to think of a way to measure that a categorical distribution of any size is concentrated in only a few bins, so not uniform. The best way I can think of is checking entropy, but that's kind of hard to assess, because either it's close to a uniform distribution or it's not unless there's something I'm missing. I also heard about kurtosis, but that's more a metric of tailedness. I'd like that, for example, if I have 5 classes and 2 of the classes make up 80% of the distribution, then I have a metric that can reflect this.

You can use the classical occupancy test
Another test you can use here is the classical occupancy test which uses the classical occupancy distribution (see e.g., O'Neill 2021).  If the true distribution is uniform over the categories then the number of occupied bins with $n$ balls allocated over $m$ bins is the classical occupancy distribution.  Moreover, any deviation from uniformity will tend to decrease the number of occupied bins, since it tends to cause balls to concentrate in a smaller number of bins.  Consequently, the occupancy number  can be used as a test statistic, with lower values more conducive to the alternative hypothesis of non-uniformity.

Implementation: Here is some code in R to create the classical occupancy test.  The test computes the p-value using the occupancy distribution in the occupancy package.  The function takes in a value n for the number of balls, m for the number of bins and occupancy for the occupancy number in the data.  The null hypothesis of the test is that the allocation is uniform and the alternative hypothesis is that it is non-uniform.
occupancy.test <- function(n, m, occupancy) {
  
  #Check inputs
  if (!is.numeric(n))      { stop("Error: Input n should be a positive integer") }
  if (length(n) != 1)      { stop("Error: Input n should be a single positive integer") }
  if (n != as.integer(n))  { stop("Error: Input n should be a positive integer") }
  if (n <= 0)              { stop("Error: Input n should be a positive integer") }
  if (!is.numeric(m))      { stop("Error: Input m should be a positive integer") }
  if (length(m) != 1)      { stop("Error: Input m should be a single positive integer") }
  if (m != as.integer(m))  { stop("Error: Input m should be a positive integer") }
  if (m <= 0)              { stop("Error: Input m should be a positive integer") }
  if (!is.numeric(occupancy)) { stop("Error: Input occupancy should be an integer") }
  k <- as.integer(occupancy)
  if (length(k) != 1)         { stop("Error: Input occupancy should be a single integer") }
  if (k != occupancy)        { stop("Error: Input occupancy should be an integer") }
  if (occupancy < 0)         { stop("Error: Input occupancy should be a positive integer") }
  if (occupancy > min(n,m))  { stop("Error: Input occupancy cannot be larger than n or m") }

#Set test content
method      <- 'Classical occupancy test'
data.name   <- paste0('Occupancy number ', occupancy, ' from allocating ', n, 
                      ' balls to ', m, ' bins')
alternative <- 'Allocation distribution is non-uniform'
statistic   <- k
attr(statistic, 'names') <- 'occupancy number'
p.value     <- occupancy::pocc(k, size = n, space = m)

#Create htest object
TEST        <- list(method = method, data.name = data.name,
                    null.value = NULL, alternative = alternative,
                    statistic = statistic, p.value = p.value)
class(TEST) <- 'htest'
TEST }

Here is an example of a classical occupancy test for $n = 18$ data points over $m = 12$ bins.  In the example we generate data from a random categorical distribution using uniformity over the categories.  We get a p-value of $0.8264271$, so we accept the null hypothesis of uniformity.
#Generate some random categorical data (for the uniform case)
set.seed(1)
DATA <- sample.int(12, size = 18, replace = TRUE)

#Compute and print occupancy number
OCC <- length(unique(DATA))
OCC
[1] 10

#Conduct the classical occupancy test
occupancy.test(n = 18, m = 12, occupancy = OCC)

        Classical occupancy test

data:  Occupancy number 10 from allocating 18 balls to 12 bins
occupancy number = 10, p-value = 0.8264
alternative hypothesis: Allocation distribution is non-uniform

Here is another example of a classical occupancy test for $n = 18$ data points over $m = 12$ bins.  In the example we generate data from a random categorical distribution where the probabilities are concentrated on only a few bins.  We get a p-value of $0.0001046$, so we reject the null hypothesis of uniformity.
#Set non-uniform bin probabilities (concentrated on only a few bins)
PROBS <- c(0.01, 0.31, 0.01, 0.01, 0.01, 0.01, 0.44, 0.16, 0.01, 0.01, 0.01, 0.01)

#Generate some random categorical data (for the non-uniform case)
set.seed(1)
DATA2 <- sample.int(12, size = 18, replace = TRUE, prob = PROBS )

#Compute and print occupancy number
OCC2 <- length(unique(DATA2))
OCC2
[1] 5

#Compute and print p-value for classical occupancy test
occupancy.test(n = 18, m = 12, occupancy = OCC2)

        Classical occupancy test

data:  Occupancy number 5 from allocating 18 balls to 12 bins
occupancy number = 5, p-value = 0.0001046
alternative hypothesis: Allocation distribution is non-uniform

