Explore longitudinal data with brolgar

ggplot2
rstats
statistics
data visualisation
longitudinal data
panel data
Published

2019-08-13

Let’s say we’ve managed to find an interesting dataset 1 on heights for given countries since 1550 (yup!):

country year height_cm continent
Afghanistan 1870 168.40 Asia
Afghanistan 1880 165.69 Asia
Afghanistan 1930 166.80 Asia
Afghanistan 1990 167.10 Asia
Afghanistan 2000 161.40 Asia
Albania 1880 170.10 Europe

We’ve got country, year, height (in centimetres), and continent. Neat!

Now, let’s look at it over time.

Oh no. What is even happening here?

This might seem familiar to you. Looking at longitudinal (panel) data often yields these kinds of “spaghetti” plots. These can be frustrating to deal with, as it is not clear how to see the right features in the data. Or even how to see some of your data.

I’ve spent a fair bit of time this year with Di Cook and also Tania Prvan thinking about some ways to improve how we look at and explore longitudinal data. It is a hard problem, and I’m certainly not done yet, but we created the brolgar package to make it easier to explore and visualise longitudinal data.

Why the name brolgar? It is an acronym, standing for:

It is so named after the “brolga”, a beautiful, gregarious (yes, gregarious!) native Australian crane:

Brolgas Healesville.jpg
By Felix Andrews (Floybix) - Own work, CC BY-SA 3.0, Link

Similar to our work on missing data with the naniar package, we wanted brolgar to work well with existing packages in the tidyverse, with particular focus on dplyr and ggplot2.

I’ll direct you to the official brolgar website for more details on the package, but here I will showcase three ideas that I think make workflow significantly more efficient, and help you learn from your data.

Idea 1: Longitudinal data is (non-typical) a time series

Not your typical time series - it generally has irregular amounts of time between measurements, and contains multiple time series. There are typically many individuals or measurements relating back to one identifying feature.

So, to efficiently look at your longitudinal data, we assume it is a time series, with irregular time periods between measurements.

This might seem strange, (that’s OK!). There are two important things to remember:

  1. The key variable in your data is the identifier of your individuals or series.
  2. The index variable is the time component of your data.

Together, the index and key uniquely identify an observation.

The term key is used a lot in brolgar, so it is an important idea to internalise:

The key is the identifier of your individuals or series

So in the heights data, we have the following setup:

heights <- as_tsibble(x = heights,
                      key = countries,
                      index = year,
                      regular = FALSE)

Here as_tsibble() takes heights, and a key (countries), and index (year), and we state the regular = FALSE (since there are not regular time periods between measurements).

This creates a tsibble object - a powerful data abstraction for time series data, made available in the tsibble package by Earo Wang:

heights
## # A tsibble: 1,499 x 4 [!]
## # Key:       country [153]
##    country      year height_cm continent
##    <chr>       <dbl>     <dbl> <chr>    
##  1 Afghanistan  1870      168. Asia     
##  2 Afghanistan  1880      166. Asia     
##  3 Afghanistan  1930      167. Asia     
##  4 Afghanistan  1990      167. Asia     
##  5 Afghanistan  2000      161. Asia     
##  6 Albania      1880      170. Europe   
##  7 Albania      1890      170. Europe   
##  8 Albania      1900      169. Europe   
##  9 Albania      2000      168. Europe   
## 10 Algeria      1910      169. Africa   
## # … with 1,489 more rows

Once we consider our longitudinal data a time series, we gain access to a set of amazing tools from the tidyverts team. What this means is that now that we know what your time index is, and what represents each series with a key, we can use that information to make sure we respect the structure in the data. This means you can spend more time performing analysis, and using functions fluently, and less time remembering to tell R about the structure of your data.

Currently brolgar only works with tsibble objects, but in the future we may look into extending this to work with regular data.frames.

If you want to learn more about what longitudinal data as a time series, you can read more in the vignette, “Longitudinal Data Structures”. And if you would like to learn more about tsibble, see the official package documentation or read the paper.

Idea 2: An opportunity to explore individuals, with various tools, and maintain information about the whole. We are more than our average.

To get a sense of what your data is, you can sometimes get something out of looking at a small sample. We’ve got some really neat tools for that.

Sample with sample_n_keys()

In dplyr, you can use sample_n() to sample n observations. Similarly, with brolgar, you can take a random sample of n keys using sample_n_keys(), or take a fraction (just as in dplyr::sample_frac()), using sample_frac_keys(), which samples a fraction of available keys.

So you could sample three keys like so:

# seed set for reproducibility
set.seed(2019-08-07)
sample_n_keys(heights, size = 3)
## # A tsibble: 21 x 4 [!]
## # Key:       country [3]
##    country     year height_cm continent
##    <chr>      <dbl>     <dbl> <chr>    
##  1 Bangladesh  1850      162. Asia     
##  2 Bangladesh  1860      162. Asia     
##  3 Bangladesh  1870      164. Asia     
##  4 Bangladesh  1880      162. Asia     
##  5 Bangladesh  1950      162. Asia     
##  6 Bangladesh  1960      162. Asia     
##  7 Bangladesh  1980      162. Asia     
##  8 Bangladesh  1990      160. Asia     
##  9 Bangladesh  2000      163. Asia     
## 10 Jamaica     1890      170. Americas 
## # … with 11 more rows

And you can then create a plot with a similar process:

library(brolgar)
library(ggplot2)
# seed set for reproducibility
set.seed(2019-08-07)
heights %>%
  sample_n_keys(size = 10) %>%
  ggplot(aes(x = year,
             y = height_cm,
             group = country)) + 
  geom_line()

OK so now we can see that for this small sample, it looks like they heights are generally increasing over time, but there is some pretty massive fluctuation as well!

Filtering observations by number of observations

You can calculate the number of observations for each key with add_n_obs():

add_n_obs(heights)
## # A tsibble: 1,499 x 5 [!]
## # Key:       country [153]
##    country      year n_obs height_cm continent
##    <chr>       <dbl> <int>     <dbl> <chr>    
##  1 Afghanistan  1870     5      168. Asia     
##  2 Afghanistan  1880     5      166. Asia     
##  3 Afghanistan  1930     5      167. Asia     
##  4 Afghanistan  1990     5      167. Asia     
##  5 Afghanistan  2000     5      161. Asia     
##  6 Albania      1880     4      170. Europe   
##  7 Albania      1890     4      170. Europe   
##  8 Albania      1900     4      169. Europe   
##  9 Albania      2000     4      168. Europe   
## 10 Algeria      1910     5      169. Africa   
## # … with 1,489 more rows

This means you can combine add_n_obs() and sample_n_keys() with filter() to filter keys with say, greater than 5 observations:

# set seed for reproducibility
set.seed(2019-08-07)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
heights %>%
  add_n_obs() %>%
  filter(n_obs > 5) %>%
  sample_n_keys(size = 10) %>%
  ggplot(aes(x = year,
             y = height_cm,
             group = country)) + 
  geom_line()

It looks like we have a bit of a mixed bag in terms of some countries increasing a lot over time, and others not so much.

Can we break these into many plots?

Sample with facet_sample()

facet_sample() allows you to plot many samples of your data. All you need to do is specify the number of keys per facet, and the number of facets with n_per_facet and n_facets. By default, it splits the data into 12 facets with 5 per facet:

# set seed for reproducibility
set.seed(2019-08-07)

ggplot(heights,
       aes(x = year,
           y = height_cm,
           group = country)) +
  geom_line() +
  facet_sample()

Interesting - looks again like many are increasing, but there is some variation and some serious wiggliness!

Fin

There is more to come for brolgar, and the API will likely undergo some changes as I get feedback from the community, and as I think and learn more about exploring longitudinal data. You can see my current thoughts on what to include in brolgar in the brolgar issues.

If you have any thoughts, comments, problems, or concerns, post a comment below or even better file an issue.

Happy data exploring!

Acknowledgements

Thank you to Mitchell O’Hara-Wild and Earo Wang for many useful discussions on the implementation of brolgar, as it was heavily inspired by the feasts package from the tidyverts. I would also like to thank Tania Prvan for her valuable early contributions to the project, as well as Stuart Lee for helpful discussions.

Footnotes

  1. For more information, see the article: “Why are you tall while others are short? Agricultural production and other proximate determinants of global heights”, Joerg Baten and Matthias Blum, European Review of Economic History 18 (2014), 144–165. Data available from http://hdl.handle.net/10622/IAEKLA, accessed via the Clio Infra website.↩︎