Chao Liu Chao Liu - 3 months ago 10
R Question

How to produce a nicer plot for my categorical time series data in a matrix?

I would like to plot each row of this matrix on separate plot in a graphical window.

mat <-
structure(c("g", "b", "c", "e", "g", "b", "g", "g", "e", "e",
"a", "b", "b", "e", "c", "f", "d", "f", "g", "c", "f", "g", "b",
"e", "a", "b", "c", "a", "c", "g", "c", "d", "e", "d", "b", "f",
"e", "f", "a", "f", "c", "f", "e", "f", "d", "d", "f", "a", "d",
"f"), .Dim = c(5L, 10L))

# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#[1,] "g" "b" "a" "f" "f" "b" "c" "f" "c" "d"
#[2,] "b" "g" "b" "d" "g" "c" "d" "e" "f" "f"
#[3,] "c" "g" "b" "f" "b" "a" "e" "f" "e" "a"
#[4,] "e" "e" "e" "g" "e" "c" "d" "a" "f" "d"
#[5,] "g" "e" "c" "c" "a" "g" "b" "f" "d" "f"


From the answer to my yesterday's post, I need to convert this matrix to numerical first.

v <- as.character(mat)
lev <- sort(unique(v)) ## sorted unique labels

# [1] "a" "b" "c" "d" "e" "f" "g"

mat_int <- matrix(match(v, lev), nrow = nrow(mat))

# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#[1,] 7 2 1 6 6 2 3 6 3 4
#[2,] 2 7 2 4 7 3 4 5 6 6
#[3,] 3 7 2 6 2 1 5 6 5 1
#[4,] 5 5 5 7 5 3 4 1 6 4
#[5,] 7 5 3 3 1 7 2 6 4 6


Now I am using the following code to generate my graph.

par(mfrow=c(5,1))

matplot(t(mat_int)[, c(1)], yaxt = "n", type = "l", xlab = "time", ylab = "category")
axis(2, seq_along(lev), labels = lev)

matplot(t(mat_int)[, c(2)], yaxt = "n", type = "l", xlab = "time", ylab = "category")
axis(2, seq_along(lev), labels = lev)

matplot(t(mat_int)[, c(3)], yaxt = "n", type = "l", xlab = "time", ylab = "category")
axis(2, seq_along(lev), labels = lev)

matplot(t(mat_int)[, c(4)], yaxt = "n", type = "l", xlab = "time", ylab = "category")
axis(2, seq_along(lev), labels = lev)

matplot(t(mat_int)[, c(5)], yaxt = "n", type = "l", xlab = "time", ylab = "category")
axis(2, seq_along(lev), labels = lev)


enter image description here

But I have several issues:


  1. The label on the y-axis for each plot of the five contains only partial results (say the 2nd plot is missing "a"). Is there a way we can list all the categorical variables on y-axis for all five of the plots? (That is to say, every plot has labels: a,b,c,d,e,f,g.

  2. Right now I have to produce this plot on a large page, in order to display all y-axis labels clearly. Is there any way to arrange my plots more close together to save space, so that they could fit in a smaller page?



Thank you.

Answer

I would like to point out 3 issues.

Use plot here

Yesterday in R graphics: How to plot a sequence of characters (pure categorical time series) you were trying to plot 2 or more rows of a matrix on a single graph so I suggest matplot. But now you only want to plot different rows separately, hence the normal plot will be sufficient.

As you updated, you can use

 plot(mat_int[2,], yaxt="n", type = "l", ylim = seq_along(lev), xlab = "time", ylab = "category")

Set a common ylim

Once you decide to produce separate graphs, you want to set a common ylim so that the y-axis will be comparable between different plots. Put

ylim = c(1, length(lev))

inside each plot. Note that ylim takes a vector of length 2 giving min and max, so ylim = 1:length(lev) is wrong.

Adjust your plot margin and / or plot on a larger page

R plot has two margins. One is the outer margin for a graphical window, another is the inner margin. Margins are measured in two units: lines and inches. The related graphical parameters are:

oma: *o*uter *ma*rgin in lines
omi: *o*uter *m*argin in *i*nches
mar: inner *mar*gin in lines
mai: inner *ma*rgin in *i*nches

Often it is more convenient to use lines as unit, as x-axis labels, plot titles, etc are placed by lines, so using oma and mar instead of omi and mai gives us a better idea how to set margins according to our need. All parameters take a vector of length 4, giving margin on "bottom", "left", "top", "right", i.e., clockwise from bottom.

Usually you don't need to do anything with outer margins, and they are by default all zeros. You could check this by par(c("oma","omi")). Note that a new graphical window will be opened, but you just ignore it or close it if you want. It is impossible to enquire graphical parameters without awaken such window, see grab par values without opening a graphics device?.

We want to set inner margin at "top", "bottom" to 0, so that all plots will be vertically joint together. By doing this, we have to set outer margin at "top" and "bottom" to leave some extra space for axes and titles (if needed).

new_par <- old_par <- par(c("mar", "oma"))
new_par$mar[1] <- 0; new_par$mar[3] <- 0    ## inner bottom and top margin to 0
new_par$oma[1] <- 3; new_par$oma[3] <- 3    ## outer bottom and top margin to 3
par(new_par)    ## set new par

par(mfrow = c(5,1))

plot(mat_int[1, ], yaxt = "n", type = "l", xlab = "time", ylab = "category", xaxt = "n")
axis(3, axTicks(3))    ## place an x-axis on the top
axis(2, seq_along(lev), labels = lev)
axis(1, axTicks(1), labels = NA)    ##  draw ticks, but no labels

plot(mat_int[2, ], yaxt = "n", type = "l", xlab = "time", ylab = "category", xaxt = "n")
axis(2, seq_along(lev), labels = lev)
axis(1, axTicks(1), labels = NA)

plot(mat_int[3, ], yaxt = "n", type = "l", xlab = "time", ylab = "category", xaxt = "n")
axis(2, seq_along(lev), labels = lev)
axis(1, axTicks(1), labels = NA)

plot(mat_int[4, ], yaxt = "n", type = "l", xlab = "time", ylab = "category", xaxt = "n")
axis(2, seq_along(lev), labels = lev)
axis(1, axTicks(1), labels = NA)

plot(mat_int[5, ], yaxt = "n", type = "l", xlab = "time", ylab = "category")
axis(2, seq_along(lev), labels = lev)

enter image description here