One option is to convert the discrete scale x to continuous to simplify the calculation of break positions:
# numeric version of x values data$x <- as.integer(as.factor(data$name))
1.x ticks between groups of bars
x_tick <- head(unique(data$x), -1) + 0.5 len <- length(x_tick) ggplot(data, aes(x = x, y = value, fill = variable)) + geom_col(position = "dodge") + scale_x_continuous(breaks = c(sort(unique(data$x)), x_tick), labels = c(sort(unique(data$name)), rep(c(""), len))) + theme(axis.ticks.x = element_line(color = c(rep(NA, len + 1), rep("black", len))))

2: x marks before, between and after groups of bars
x_tick <- c(0, unique(data$x)) + 0.5 len <- length(x_tick) ggplot(data, aes(x = x, y = value, fill = variable)) + geom_col(position = "dodge") + scale_x_continuous(breaks = c(sort(unique(data$x)), x_tick), labels = c(sort(unique(data$name)), rep(c(""), len))) + theme(axis.ticks.x = element_line(color = c(rep(NA, len - 1), rep("black", len))))

Do not ask me about additional grid lines that appeared in 2.25 and 1.75 respectively ...
source share