Search code examples
rlattice

How to set up labels of stacked bar charts created with lattice in R?


Some time ago I solved to put labels as percentages in stacked bar charts created with the likert function from the HH package, that uses lattice. My data was answers to a Likert-type scale with an even number of levels and the code works as I expected.

+---+-------------------+-------------------+-------------------+----------------+----------------+----------------+--------------------+
|   | Strongly Disagree | Moderate Disagree | Slightly Disagree | Slightly Agree | Moderate Agree | Strongly Agree | Group              |
+===+===================+===================+===================+================+================+================+====================+
| 1 | 2.00              | 1.00              | 3.00              | 1.00           | 4.00           | 9.00           | Experimental group |
+---+-------------------+-------------------+-------------------+----------------+----------------+----------------+--------------------+
| 2 | 1.00              | 2.00              | 1.00              | 5.00           | 5.00           | 6.00           | Control group      |
+---+-------------------+-------------------+-------------------+----------------+----------------+----------------+--------------------+

enter image description here When I tried to use my code with an odd number of levels I noticed an strange issue, the percentages that represents the middle answer are divided into two equal parts, that was not an expected behavior. In the middle section we must see the sum of that two percentages. How to solve this issue?

+---+-------------------+-------------------+----------------------------+----------------+----------------+--------------------+
|   | Strongly Disagree | Moderate Disagree | Neither Agree nor Disagree | Moderate Agree | Strongly Agree | Group              |
+===+===================+===================+============================+================+================+====================+
| 1 | 0.00              | 0.00              | 9.00                       | 10.00          | 1.00           | Experimental Group |
+---+-------------------+-------------------+----------------------------+----------------+----------------+--------------------+
| 2 | 1.00              | 5.00              | 10.00                      | 4.00           | 0.00           | Control Group      |
+---+-------------------+-------------------+----------------------------+----------------+----------------+--------------------+

enter image description here

The data with an even number of levels:

data.freq <- structure(list(`Strongly Disagree` = c(2L, 1L), `Moderate Disagree` = 1:2, `Slightly Disagree` = c(3L, 1L), `Slightly Agree` = c(1L, 5L), `Moderate Agree` = 4:5, `Strongly Agree` = c(9L, 6L), Group = c("Experimental group", "Control group")), .Names = c("Strongly Disagree", "Moderate Disagree", "Slightly Disagree", "Slightly Agree", "Moderate Agree", "Strongly Agree", "Group"), row.names = c("1", "2"), class = "data.frame")

The data with an odd number of levels:

data.freq <- structure(list(`Strongly Disagree` = 0:1, `Moderate Disagree` = c(0L, 5L), `Neither Agree nor Disagree.` = 9:10, `Moderate Agree` = c(10L, 4L), `Strongly Agree` = c(1L, 0L), Group = c("Experimental Group", "Control Group")), .Names = c("Strongly Disagree", "Moderate Disagree", "Neither Agree nor Disagree", "Moderate Agree", "Strongly Agree", "Group"), row.names = c("1", "2"), class = "data.frame")

The code:

library(HH)
ppi <- 150
jpeg("ssb_%02d.jpg", width=7*ppi, height=4*ppi, res=ppi)
scales.lab <- seq(-100, 100, by = 20)

plot_obj <- likert(Group ~ . | Group, data = data.freq, as.percent = TRUE, positive.order = TRUE,
    main="", xlab="",
    ylab="", ylab.right = list("Subjects per group", cex=1.1),
    scales = list(y = list(relation = "free", labels=""), cex=1.1, 
        x = list(at=scales.lab, labels=paste(abs(scales.lab), "%", sep = "")), cex = 0.8),
    layout = c(1, 2), auto.key=list(space="bottom", columns=3, cex.title=1.1, title="Levels", cex=1.1, size = 1, between.columns=0.5))

plot_obj <- plot_obj +
    layer({
        id = which(x > 0)
        xx = 0.5 * (cumsum(x[id]) + cumsum(c(0, x[id][-length(id)])))
        keep = x[id] >= 5
        panel.text(xx[keep], y[id][keep], labels = paste(x[id][keep], "%", sep = ""), cex = 0.8, srt = 45)
        id = which(x < 0)
        xx = 0.5 * (cumsum(x[id]) + cumsum(c(0, x[id][-length(id)])))
        keep = x[id] <= -5
        panel.text(xx[keep], y[id][keep], labels = paste(-x[id][keep], "%", sep = ""), cex = 0.8, srt = 45)
    })

print(plot_obj)

dev.off()

Solution

  • ## even
    data.freq.even <- structure(list(`Strongly Disagree` = c(2L, 1L),
    `Moderate Disagree` = 1:2, `Slightly Disagree` = c(3L, 1L),
    `Slightly Agree` = c(1L, 5L), `Moderate Agree` = 4:5, `Strongly Agree`
    = c(9L, 6L), Group = c("Experimental group", "Control group")), .Names
    = c("Strongly Disagree", "Moderate Disagree", "Slightly Disagree",
    "Slightly Agree", "Moderate Agree", "Strongly Agree", "Group"),
    row.names = c("1", "2"), class = "data.frame")
    
    legend.labels.even <- c("Strongly\nDisagree", "Moderate\nDisagree", "Slightly\nDisagree",
    "Slightly\nAgree", "Moderate\nAgree", "Strongly\nAgree")
    
    ## odd
    data.freq.odd <- structure(list(`Strongly Disagree` = 0:1,
    `Moderate Disagree` = c(0L, 5L), `Neither Agree nor Disagree.` = 9:10,
    `Moderate Agree` = c(10L, 4L), `Strongly Agree` = c(1L, 0L), Group =
    c("Experimental Group", "Control Group")), .Names =
    c("Strongly Disagree", "Moderate Disagree",
    "Neither Agree nor Disagree", "Moderate Agree", "Strongly Agree",
    "Group"), row.names = c("1", "2"), class = "data.frame")
    
    legend.labels.odd <- c("Strongly\nDisagree", "Moderate\nDisagree",
    "Neither Agree\nnor Disagree", "Moderate\nAgree", "Strongly\nAgree")
    
    library(HH)
    
    scales.lab <- seq(-100, 100, by = 20)
    
    MalaiPlot <- function(data.freq, legend.labels, legend.columns,
                          data.columns=c(left=3, middle=1, right=3), ## Assumption: 7 columns with three left, one middle, and three right.
                          ...) {
    
      plot_obj <- likert(Group ~ . | Group, data = data.freq, as.percent = TRUE, positive.order = TRUE,
                         main="", xlab="",
                         ylab="", ylab.right = list("Subjects per group", cex=1.1),
                         scales = list(y = list(relation = "free", labels=""), cex=1.1,
                           x = list(at=scales.lab, labels=paste(abs(scales.lab), "%", sep = "")), cex = 0.8),
                         layout = c(1, 2),
                         auto.key=list(
                           space="bottom", columns=3, cex.title=1.1,
                           title="Levels", cex=1.1, size = 1, between.columns=0.5),
                         data.columns=data.columns,
                         ...)
    
      plot_obj <- plot_obj +
        layer({
          if (data.columns["middle"] == 0) { ## even
    
            left <- seq(from=1, length=data.columns["left"])
            middle <- integer(0)
            right <- seq(from=data.columns["left"]+1, length=data.columns["right"])
    
            xx <- 0.5 * (cumsum(x[right]) + cumsum(c(0, x[right][-length(right)])))
            keep <- x[right] >= 5
            panel.text(xx[keep], y[right][keep], labels = paste(x[right][keep], "%", sep = ""), cex = 0.8, srt = 45)
    
            xx = 0.5 * (cumsum(x[left]) + cumsum(c(0, x[left][-length(left)])))
            keep = x[left] <= -5
            panel.text(xx[keep], y[left][keep], labels = paste(-x[left][keep], "%", sep = ""), cex = 0.8, srt = 45)
          } else { ## odd
    
            left <- seq(from=2, length=data.columns["left"])
            middle <- c(1, data.columns["left"]+2)
            right <- seq(data.columns["left"]+3, length=data.columns["right"])
    
            xx <- (0.5 * (cumsum(x[c(middle[2], right)]) + cumsum(c(0, x[c(middle[2], right[-length(right)])]))))[-1]
            keep <- x[right] >= 5
            panel.text(xx[keep], y[right][keep], labels = paste(x[right][keep], "%", sep = ""), cex = 0.8, srt = 45)
    
            xx <- 0
            keep <- sum(abs(x)[middle]) >= 5
            panel.text(xx[keep], y[middle][keep], labels = paste(sum(abs(x)[middle])[keep], "%", sep = ""), cex = 0.8, srt = 45)
    
    
           xx <- (0.5 * (cumsum(abs(x)[c(middle[1], left)]) + cumsum(c(0, abs(x)[c(middle[1], left[-length(left)])]))))[-1]
            keep = x[left] <= -5
            panel.text(-xx[keep], y[left][keep], labels = paste(-x[left][keep], "%", sep = ""), cex = 0.8, srt = 45)
          }
        }, data=list(data.columns=data.columns))
    
      if (!missing(legend.labels))
        plot_obj$legend$bottom$args$text <- legend.labels
      if (!missing(legend.columns))
        plot_obj$legend$bottom$args$columns <- legend.columns
    
    plot_obj
    }
    
    MalaiPlot(data.freq.odd, legend.labels=legend.labels.odd, legend.columns=5, data.columns=c(left=2, middle=1, right=2))
    
    MalaiPlot(data.freq.even, legend.labels=legend.labels.even, legend.columns=6, data.columns=c(left=3, middle=0, right=3))