studio graphs r graphics visualization data-visualization

graphs - Buenas formas de visualizar datos categóricos longitudinales en R



r graph gallery ggplot2 (3)

[ Actualización: Aunque he aceptado una respuesta, agregue otra respuesta si tiene ideas de visualización adicionales (ya sea en R u otro idioma / programa). Los textos sobre el análisis de datos categóricos no parecen decir mucho sobre la visualización de datos longitudinales, mientras que los textos sobre el análisis de datos longitudinales no parecen decir mucho sobre la visualización de los cambios dentro del tema a lo largo del tiempo en la categoría de miembros. Tener más respuestas a esta pregunta lo convertirá en un mejor recurso en un problema que no recibe mucha cobertura en las referencias estándar.]

Un colega me acaba de dar un conjunto de datos categóricos longitudinales para ver y estoy intentando descubrir cómo capturar el aspecto longitudinal en una visualización. Estoy publicando aquí, porque me gustaría hacer esto en R, pero por favor avíseme si también tiene sentido hacer una cruzación cruzada a una validación cruzada, ya que la publicación cruzada generalmente no se recomienda.

Antecedentes rápidos: los datos rastrean la situación académica de un término a otro para los estudiantes que pasaron por un programa de asesoramiento académico. Los datos están en formato largo y tienen cinco variables: "id", "cohorte", "term", "standing" y "termGPA". Los dos primeros identifican al estudiante y el término en el que estaban en el programa de asesoramiento. Los últimos tres son los términos en que se registraron la situación académica y el GPA del estudiante. He pegado en algunos datos de muestra a continuación utilizando dput .

He creado un diagrama de mosaico (ver más abajo) que agrupa a los estudiantes por cohorte, posición y término. Esto muestra qué fracción de los estudiantes estaban en cada categoría académica en cada término. Pero esto no capta el aspecto longitudinal, el hecho de que se realiza un seguimiento de los estudiantes individuales a lo largo del tiempo. Me gustaría rastrear el camino que los grupos de estudiantes con una posición académica determinada toman a lo largo del tiempo.

Por ejemplo: de los estudiantes con calificación "AP" (período de prueba académico) en otoño de 2009 ("F09"), ¿qué fracción todavía era AP en términos futuros y qué fracción se trasladó a otras categorías (p. Ej., GS, "buena reputación")? ¿Existen diferencias entre las cohortes en términos de movimiento entre categorías con el tiempo desde la entrada en el programa de asesoramiento?

No podía entender cómo capturar este aspecto longitudinal en una gráfica R El paquete vcd tiene facilidades para visualizar datos categóricos, pero no parece abordar datos categóricos longitudinales . ¿Existen métodos "estándar" para visualizar datos categóricos longitudinales? ¿R tiene paquetes diseñados para esto? ¿Es el formato largo apropiado para este tipo de datos o estaría mejor con formato ancho?

Apreciaría sugerencias para resolver este problema en particular y también sugerencias para artículos, libros, etc. para aprender más sobre cómo visualizar datos categóricos longitudinales.

Aquí está el código que usé para hacer la trama del mosaico. El código utiliza los datos enumerados a continuación con el dput .

library(RColorBrewer) # create a table object for plotting df1.tab = table(df1$cohort, df1$term, df1$standing, dnn=c("Cohort/nAcademic Standing", "Term", "Standing")) # create a mosaic plot plot(df1.tab, las=1, dir=c("h","v","h"), col=brewer.pal(8,"Dark2"), main="Fall 2009 and Fall 2010 Cohorts")

Aquí está el diagrama de mosaico (pregunta complementaria: ¿hay alguna manera de hacer que las columnas de la cohorte F10 se sienten directamente debajo y tengan el mismo ancho que las columnas de la cohorte F09, incluso cuando no hay datos para algunos términos en la cohorte F10?) :

Y aquí están los datos utilizados para crear la tabla y la trama:

df1 = structure(list(id = c(101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L, 111L, 112L, 113L, 114L, 115L, 116L, 117L, 118L, 119L, 120L, 121L, 122L, 123L, 124L, 125L), cohort = structure(c(1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 2L), .Label = c("F09", "F10"), class = c("ordered", "factor")), term = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L), .Label = c("S09", "F09", "S10", "F10", "S11", "F11", "S12"), class = c("ordered", "factor")), standing = structure(c(2L, 4L, 1L, 4L, NA, 4L, 1L, NA, NA, NA, NA, 2L, 2L, 1L, 4L, 4L, 1L, 3L, NA, NA, 4L, 3L, 1L, 4L, NA, 2L, 1L, 3L, 3L, NA, 1L, 2L, NA, NA, NA, NA, 2L, 4L, 3L, 4L, 4L, 4L, 2L, NA, NA, 4L, 2L, 4L, 4L, NA, 3L, 4L, 6L, 6L, 1L, 4L, 4L, 1L, 1L, 1L, 1L, 1L, 4L, 6L, 4L, 4L, 1L, 4L, 1L, 2L, 4L, 3L, 1L, 4L, 1L, 6L, 1L, 6L, 6L, 7L, 4L, 4L, 2L, 2L, 4L, 2L, 6L, 4L, 6L, 7L, 4L, 2L, 4L, 1L, 2L, 4L, 6L, 6L, 4L, 2L, 2L, 3L, 6L, 6L, 7L, 4L, 4L, 3L, 4L, 4L, 6L, 2L, 1L, 6L, 6L, 4L, 2L, 1L, 7L, 2L, 4L, 6L, 6L, 4L, 4L, 3L, 6L, 4L, 6L, 2L, 4L, 4L, 6L, 4L, 4L, 6L, 3L, 2L, 6L, 6L, 4L, 2L, 6L, 3L, 4L, 4L, 6L, 6L, 4L, 4L, 5L, 6L, 4L, 6L, 4L, 4L, 4L, 5L, 4L, 4L, 6L, 6L, 2L, 6L, 6L, 4L, 3L, 6L, 6L, 4L, 4L, 6L, 6L, 4L, 4L), .Label = c("AP", "CP", "DQ", "GS", "DM", "NE", "WD"), class = "factor"), termGPA = c(1.433, 1.925, 1, 1.68, NA, 1.579, 1.233, NA, NA, NA, NA, 2.009, 1.675, 0, 1.5, 1.86, 0.5, 0.94, NA, NA, 1.777, 1.1, 1.133, 1.675, NA, 2, 1.25, 1.66, 0, NA, 1.525, 2.25, NA, NA, NA, NA, 1.66, 2.325, 0, 2.308, 1.6, 1.825, 2.33, NA, NA, 2.65, 2.65, 2.85, 3.233, NA, 1.25, 1.575, NA, NA, 1, 2.385, 3.133, 0, 0, 1.729, 1.075, 0, 4, NA, 2.74, 0, 1.369, 2.53, 0, 2.65, 2.75, 0, 0.333, 3.367, 1, NA, 0.1, NA, NA, 1, 2.2, 2.18, 2.31, 1.75, 3.073, 0.7, NA, 1.425, NA, 2.74, 2.9, 0.692, 2, 0.75, 1.675, 2.4, NA, NA, 3.829, 2.33, 2.3, 1.5, NA, NA, NA, 2.69, 1.52, 0.838, 2.35, 1.55, NA, 1.35, 0.66, NA, NA, 1.35, 1.9, 1.04, NA, 1.464, 2.94, NA, NA, 3.72, 2.867, 1.467, NA, 3.133, NA, 1, 2.458, 1.214, NA, 3.325, 2.315, NA, 1, 2.233, NA, NA, 2.567, 1, NA, 0, 3.325, 2.077, NA, NA, 3.85, 2.718, 1.385, NA, 2.333, NA, 2.675, 1.267, 1.6, 1.388, 3.433, 0.838, NA, NA, 0, NA, NA, 2.6, 0, NA, NA, 1, 2.825, NA, NA, 3.838, 2.883)), .Names = c("id", "cohort", "term", "standing", "termGPA"), row.names = c("101.F09.s09", "102.F09.s09", "103.F09.s09", "104.F09.s09", "105.F10.s09", "106.F09.s09", "107.F09.s09", "108.F10.s09", "109.F10.s09", "110.F10.s09", "111.F10.s09", "112.F09.s09", "113.F09.s09", "114.F09.s09", "115.F09.s09", "116.F09.s09", "117.F09.s09", "118.F09.s09", "119.F10.s09", "120.F10.s09", "121.F09.s09", "122.F09.s09", "123.F09.s09", "124.F09.s09", "125.F10.s09", "101.F09.f09", "102.F09.f09", "103.F09.f09", "104.F09.f09", "105.F10.f09", "106.F09.f09", "107.F09.f09", "108.F10.f09", "109.F10.f09", "110.F10.f09", "111.F10.f09", "112.F09.f09", "113.F09.f09", "114.F09.f09", "115.F09.f09", "116.F09.f09", "117.F09.f09", "118.F09.f09", "119.F10.f09", "120.F10.f09", "121.F09.f09", "122.F09.f09", "123.F09.f09", "124.F09.f09", "125.F10.f09", "101.F09.s10", "102.F09.s10", "103.F09.s10", "104.F09.s10", "105.F10.s10", "106.F09.s10", "107.F09.s10", "108.F10.s10", "109.F10.s10", "110.F10.s10", "111.F10.s10", "112.F09.s10", "113.F09.s10", "114.F09.s10", "115.F09.s10", "116.F09.s10", "117.F09.s10", "118.F09.s10", "119.F10.s10", "120.F10.s10", "121.F09.s10", "122.F09.s10", "123.F09.s10", "124.F09.s10", "125.F10.s10", "101.F09.f10", "102.F09.f10", "103.F09.f10", "104.F09.f10", "105.F10.f10", "106.F09.f10", "107.F09.f10", "108.F10.f10", "109.F10.f10", "110.F10.f10", "111.F10.f10", "112.F09.f10", "113.F09.f10", "114.F09.f10", "115.F09.f10", "116.F09.f10", "117.F09.f10", "118.F09.f10", "119.F10.f10", "120.F10.f10", "121.F09.f10", "122.F09.f10", "123.F09.f10", "124.F09.f10", "125.F10.f10", "101.F09.s11", "102.F09.s11", "103.F09.s11", "104.F09.s11", "105.F10.s11", "106.F09.s11", "107.F09.s11", "108.F10.s11", "109.F10.s11", "110.F10.s11", "111.F10.s11", "112.F09.s11", "113.F09.s11", "114.F09.s11", "115.F09.s11", "116.F09.s11", "117.F09.s11", "118.F09.s11", "119.F10.s11", "120.F10.s11", "121.F09.s11", "122.F09.s11", "123.F09.s11", "124.F09.s11", "125.F10.s11", "101.F09.f11", "102.F09.f11", "103.F09.f11", "104.F09.f11", "105.F10.f11", "106.F09.f11", "107.F09.f11", "108.F10.f11", "109.F10.f11", "110.F10.f11", "111.F10.f11", "112.F09.f11", "113.F09.f11", "114.F09.f11", "115.F09.f11", "116.F09.f11", "117.F09.f11", "118.F09.f11", "119.F10.f11", "120.F10.f11", "121.F09.f11", "122.F09.f11", "123.F09.f11", "124.F09.f11", "125.F10.f11", "101.F09.s12", "102.F09.s12", "103.F09.s12", "104.F09.s12", "105.F10.s12", "106.F09.s12", "107.F09.s12", "108.F10.s12", "109.F10.s12", "110.F10.s12", "111.F10.s12", "112.F09.s12", "113.F09.s12", "114.F09.s12", "115.F09.s12", "116.F09.s12", "117.F09.s12", "118.F09.s12", "119.F10.s12", "120.F10.s12", "121.F09.s12", "122.F09.s12", "123.F09.s12", "124.F09.s12", "125.F10.s12"), reshapeLong = structure(list( varying = list(c("s09as", "f09as", "s10as", "f10as", "s11as", "f11as", "s12as"), c("s09termGPA", "f09termGPA", "s10termGPA", "f10termGPA", "s11termGPA", "f11termGPA", "s12termGPA")), v.names = c("standing", "termGPA"), idvar = c("id", "cohort" ), timevar = "term"), .Names = c("varying", "v.names", "idvar", "timevar")), class = "data.frame")


Al investigar mi pregunta, he encontrado algunas otras opciones que listaré aquí.

Una serie de paquetes R relativamente nuevos están diseñados para visualizar y analizar datos de "historia de vida" o "secuencia multiestatal". La idea es que con el tiempo las personas (u objetos) entren y salgan de varias categorías, por ejemplo, cambios de carrera, matrimonio y divorcio, salud y enfermedad o, en mi caso, categorías de prestigio académico en la universidad.

Los paquetes de R para visualizar la secuencia o los datos de la historia de vida incluyen biograph , mencionadas por @timriffe en un comentario anterior y TraMineR . El autor del paquete de biografías, Frans Willekens, tiene un libro sobre el paquete, Biograph. Análisis multiestado de historias de vida con R , que será publicado por Springer este otoño. TraMineR tiene un manual de usuario detallado en el enlace anterior y también un artículo JSS más corto. JSS también tiene un número especial sobre modelos multiestado en el contexto del análisis de riesgos que trata sobre paquetes R adicionales para modelado multiestado.

También encontré un software especializado diseñado para visualizar movimientos entre categorías a lo largo del tiempo. Parallel Sets es un programa simple y gratuito para producir visualizaciones básicas, aunque tiene una flexibilidad limitada. Lifeflow es más sofisticado. También es gratuito, pero debes enviar un correo electrónico al creador solicitando una copia.

Agregaré más detalles a esta respuesta, una vez que haya tenido la oportunidad de probar estas herramientas.


Aquí hay algunas ideas para trazar sus datos. He usado ggplot2, y he reformateado un poco los datos en algunos lugares.

Figura 1

He usado una gráfica de barras apilada para imitar tu gráfica de mosaico y resolver el problema de alineación.

Figura 2

Los puntos de datos para cada estudiante están conectados por una línea gris, lo que hace que recuerde a una gráfica de coordenadas paralelas. Colorear los puntos muestra la categoría categórica. El uso de GPA en el eje y ayuda a extender los puntos para reducir el exceso de puntos, y muestra la correlación de la posición y el GPA. Un problema importante es que muchos puntos de datos standing válidos standing eliminan porque carecen de un valor termGPA coincidente.

figura 3

Aquí he creado una nueva variable llamada initial_standing para usar en facetas. Cada panel contiene estudiantes que coinciden tanto en cohorte como en initial_standing. Trazar la identificación como texto hace que esta figura esté un poco desordenada, pero podría ser útil en algunos casos.

Figura 4

Esta trama es como un mapa de calor donde cada fila es un estudiante. Controlé el orden del eje de id para obligar a los grupos de cohorte y inicialización a permanecer juntos. Si tiene muchas más filas, es posible que desee considerar la clasificación de filas por algún tipo de agrupación en clústeres.

library(ggplot2) # Create new data frame for determining initial standing. standing_data = data.frame(id=unique(df1$id), initial_standing=NA, cohort=NA) for (i in 1:nrow(standing_data)) { id = standing_data$id[i] subdat = df1[df1$id == id, ] subdat = subdat[complete.cases(subdat), ] initial_standing = subdat$standing[which.min(subdat$term)] standing_data[i, "initial_standing"] = as.character(initial_standing) standing_data[i, "cohort"] = as.character(subdat$cohort[1]) } standing_data$cohort = factor(standing_data$cohort, levels=levels(df1$cohort)) standing_data$initial_standing = factor(standing_data$initial_standing, levels=levels(df1$standing)) # Add the new column (initial_standing) to df1. df1 = merge(df1, standing_data[, c("id", "initial_standing")], by="id") # Remove rows where standing is missing. Make some plots tidier. df1 = df1[!is.na(df1$standing), ] # Create id factor, controlling the sort order of the levels. id_order = order(standing_data$initial_standing, standing_data$cohort) df1$id = factor(df1$id, levels=as.character(standing_data$id)[id_order]) p1 = ggplot(df1, aes(x=term, fill=standing)) + geom_bar(position="fill", colour="grey20", size=0.5, width=1.0) + facet_grid(cohort ~ .) + scale_fill_brewer(palette="Set1") p2 = ggplot(df1, aes(x=term, y=termGPA, group=id)) + geom_line(colour="grey70") + geom_point(aes(colour=standing), size=4) + facet_grid(cohort ~ .) + scale_colour_brewer(palette="Set1") p3 = ggplot(df1, aes(x=term, y=termGPA, group=id)) + geom_line(colour="grey70") + geom_point(aes(colour=standing), size=4) + geom_text(aes(label=id), hjust=-0.30, size=3) + facet_grid(initial_standing ~ cohort) + scale_colour_brewer(palette="Set1") p4 = ggplot(df1, aes(x=term, y=id, fill=standing)) + geom_tile(colour="grey20") + facet_grid(initial_standing ~ ., space="free_y", scales="free_y") + scale_fill_brewer(palette="Set1") + opts(panel.grid.major=theme_blank()) + opts(panel.grid.minor=theme_blank()) ggsave("plot_1.png", p1, width=10, height=6.25, dpi=80) ggsave("plot_2.png", p2, width=10, height=6.25, dpi=80) ggsave("plot_3.png", p3, width=10, height=6.25, dpi=80) ggsave("plot_4.png", p4, width=10, height=6.25, dpi=80)


Desearía haber encontrado la respuesta de @ bdemarest antes de escribir un paquete R para resolver este problema, pero como el OP solicitó actualizaciones adicionales, compartiré una solución más. Lo que bdemarest sugirió en la Figura 4 es lo que he estado llamando un tipo de gráfico de línea horizontal.

Al desarrollar el paquete longCatEDA R, descubrimos que la clasificación de los datos era crucial para realizar gráficos útiles (ver example(sorter) y el informe vinculado en el comentario a continuación para obtener detalles técnicos), especialmente a medida que el tamaño del problema se hizo grande. Por ejemplo, comenzamos el problema con los datos de consumo diario (abstinencia, uso, abuso) de varios miles de participantes durante 3 años (> 1000 días).

El código para aplicar el trazado de línea horizontal a los datos de @ eipi10 está abajo. La Figura 1 se estratifica por term , y la Figura 2 se estratifica por el primer estado como en la Figura 4 de @bdemarest, aunque los resultados no son idénticos debido a la clasificación dentro de los estratos.

Figura 1

Figura 2

# libraries install.packages(''longCatEDA'') library(longCatEDA) library(RColorBrewer) # transform data long to wide dfw <- reshape(df1, timevar = ''term'', idvar = c(''id'', ''cohort''), direction = ''wide'') # set up objects required by longCat() y <- dfw[,seq(3,15,by=2)] Labels <- levels(df1$standing) tLabels <- levels(df1$term) groupLabels <- levels(dfw$cohort) # use the same colors as bdemarest cols <- brewer.pal(7, "Set1") # plot the longCat object png(''plot1.png'', width=10, height=6.25, units=''in'', res=100) par(bg=''cornsilk3'', mar=c(5.1, 4.1, 4.1, 8.1), xpd=TRUE) lc <- longCat(y=y, Labels=Labels, tLabels=tLabels, id=dfw$id) longCatPlot(lc, cols=cols, xlab=''Term'', lwd=8, legendBuffer=0) legend(8.1, 25, legend=Labels, col=cols, lty=1, lwd=4) dev.off() # stratify by term png(''plot2.png'', width=10, height=6.25, units=''in'', res=100) par(bg=''cornsilk3'', mar=c(5.1, 4.1, 4.1, 8.1), xpd=TRUE) lc.g <- sorter(lc, group=dfw$cohort, groupLabels=groupLabels) longCatPlot(lc.g, cols=cols, xlab=''Term'', lwd=8, legendBuffer=0) legend(8.1, 25, legend=Labels, col=cols, lty=1, lwd=4) dev.off() # stratify by first status, akin to Figure 4 by bdemarest png(''plot2.png'', width=10, height=6.25, units=''in'', res=100) par(bg=''cornsilk3'', mar=c(5.1, 4.1, 4.1, 8.1), xpd=TRUE) first <- apply(!is.na(y), 1, function(x) which(x)[1]) first <- y[cbind(seq_along(first), first)] lc.1 <- sorter(lc, group=factor(first), groupLabels = sort(unique(first))) longCatPlot(lc.1, cols=cols, xlab=''Term'', lwd=8, legendBuffer=0) legend(8.1, 25, legend=Labels, col=cols, lty=1, lwd=4) dev.off()