Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I spread repeated measures of multiple variables into wide format?

Tags:

r

tidyr

I'm trying to take columns that are in long format and spread them to wide format as shown below. I'd like to use tidyr to solve this with the data manipulation tools I'm investing in but to make this answer more general please provide other solutions.

Here's what I have:

library(dplyr); library(tidyr)

set.seed(10)
dat <- data_frame(
    Person = rep(c("greg", "sally", "sue"), each=2),
    Time = rep(c("Pre", "Post"), 3),
    Score1 = round(rnorm(6, mean = 80, sd=4), 0),
    Score2 = round(jitter(Score1, 15), 0),
    Score3 = 5 + (Score1 + Score2)/2
)

##   Person Time Score1 Score2 Score3
## 1   greg  Pre     80     78   84.0
## 2   greg Post     79     80   84.5
## 3  sally  Pre     75     74   79.5
## 4  sally Post     78     78   83.0
## 5    sue  Pre     81     78   84.5
## 6    sue Post     82     81   86.5

Desired wide format:

  Person Pre.Score1 Pre.Score2 Pre.Score3  Post.Score1 Post.Score2 Post.Score3
1   greg         80         78       84.0           79          80        84.5
2  sally         75         74       79.5           78          78        83.0
3    sue         81         78       84.5           82          81        86.5

I can do it by doing something like this for each score:

spread(dat %>% select(Person, Time, Score1), Time, Score1) %>% 
    rename(Score1_Pre = Pre, Score1_Post = Post)

And then using _join but that seems verbose and like there's got to be a better way.

Related questions:
tidyr wide to long with two repeated measures
Is it possible to use spread on multiple columns in tidyr similar to dcast?

like image 977
Tyler Rinker Avatar asked Apr 21 '15 14:04

Tyler Rinker


People also ask

What is the difference between long and wide format?

A dataset can be written in two different formats: wide and long. A wide format contains values that do not repeat in the first column. A long format contains values that do repeat in the first column. Notice that in the wide dataset, each value in the first column is unique.

What data format uses a row for the elements of a measurement or observation each in a separate column?

In the wide format, a subject's repeated responses will be in a single row, and each response is in a separate column.

What is long and wide format in R?

When there are multiple measurements of the same subject, across time or using different tools, the data is often described as being in "wide" format if there is one observation row per subject with each measurement present as a different variable and "long" format if there is one observation row per measurement (thus, ...

What is long format in SPSS?

Below are two different ways to set up repeated measures data, namely, the long format and the wide format. The long format uses multiple rows for each observation or participant: ID WEIGHT CALORIES TIME 1 200 3500 1 1 190 3300 2 1 180 3100 3 2 160 3000 1 2 150 2900 2 2 140 2800 3.


4 Answers

Edit: I'm updating this answer since pivot_wider has been around for a while now and addresses the issue in this question and comments. You can now do

pivot_wider(
    dat, 
    id_cols = 'Person', 
    names_from = 'Time', 
    values_from = c('Score1', 'Score2', 'Score3'), 
    names_glue = '{Time}.{.value}'
)

to get the desired result.


The original answer was

dat %>% 
  gather(temp, score, starts_with("Score")) %>% 
  unite(temp1, Time, temp, sep = ".") %>% 
  spread(temp1, score)
like image 134
konvas Avatar answered Oct 19 '22 12:10

konvas


Using reshape2:

library(reshape2)
dcast(melt(dat), Person ~ Time + variable)

Produces:

Using Person, Time as id variables
  Person Post_Score1 Post_Score2 Post_Score3 Pre_Score1 Pre_Score2 Pre_Score3
1   greg          79          78        83.5         83         81       87.0
2  sally          82          81        86.5         75         74       79.5
3    sue          78          78        83.0         82         79       85.5
like image 45
BrodieG Avatar answered Oct 19 '22 12:10

BrodieG


Using dcast from the data.table package.

library(data.table)#v1.9.5+
dcast(setDT(dat), Person~Time, value.var=paste0("Score", 1:3))
#     Person Score1_Post Score1_Pre Score2_Post Score2_Pre Score3_Post Score3_Pre
#1:   greg          79         80          80         78        84.5       84.0
#2:  sally          78         75          78         74        83.0       79.5
#3:    sue          82         81          81         78        86.5       84.5

Or reshape from baseR

reshape(as.data.frame(dat), idvar='Person', timevar='Time',direction='wide')

Update

From development version tidyr_0.8.3.9000 or CRAN release tidyr_1.0.0, we can use pivot_wider for multiple value columns

library(tidyr)
library(stringr)
dat %>%
     pivot_wider(names_from = Time, values_from = str_c("Score", 1:3))
# A tibble: 3 x 7
#  Person Score1_Pre Score1_Post Score2_Pre Score2_Post Score3_Pre Score3_Post
#   <chr>       <dbl>       <dbl>      <dbl>       <dbl>      <dbl>       <dbl>
#1 greg           80          79         78          80       84          84.5
#2 sally          75          78         74          78       79.5        83  
#3 sue            81          82         78          81       84.5        86.5
like image 38
akrun Avatar answered Oct 19 '22 11:10

akrun


I did a benchmark for myself and post it here in case someone is interested:

Code

The setup is chosen from the OP, three variables, two time points. However, the size of the data frames is varied from 1,000 to 100,000 rows.

library(magrittr)
library(data.table)
library(bench)

f1 <- function(dat) {
    tidyr::gather(dat, key = "key", value = "value", -Person, -Time) %>% 
        tidyr::unite("id", Time, key, sep = ".") %>%
        tidyr::spread(id, value)
}

f2 <- function(dat) {
    reshape2::dcast(melt(dat, id.vars = c("Person", "Time")), Person ~ Time + variable)
}

f3 <- function(dat) {
    dcast(melt(dat, id.vars = c("Person", "Time")), Person ~ Time + variable)
}

create_df <- function(rows) {
    dat <- expand.grid(Person = factor(1:ceiling(rows/2)),
                       Time = c("1Pre", "2Post"))
    dat$Score1 <- round(rnorm(nrow(dat), mean = 80, sd = 4), 0)
    dat$Score2 <- round(jitter(dat$Score1, 15), 0)
    dat$Score3 <- 5 + (dat$Score1 + dat$Score2)/2
    return(dat)
}

Results

As you can see, reshape2 is a little bit faster than tidyr, probably because tidyr has a larger overhead. Importantly, data.table excels with > 10,000 rows.

press(
    rows = 10^(3:5),
    {
        dat <- create_df(rows)
        dat2 <- copy(dat)
        setDT(dat2)
        bench::mark(tidyr     = f1(dat),
                    reshape2  = f2(dat),
                    datatable = f3(dat2),
                    check = function(x, y) all.equal(x, y, check.attributes = FALSE),
                    min_iterations = 20
        )
    }
)
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
#> # A tibble: 9 x 11
#>   expression   rows      min     mean   median      max `itr/sec` mem_alloc
#>   <chr>       <dbl> <bch:tm> <bch:tm> <bch:tm> <bch:tm>     <dbl> <bch:byt>
#> 1 tidyr        1000    5.7ms   6.13ms   6.02ms  10.06ms    163.      2.78MB
#> 2 reshape2     1000   2.82ms   3.09ms   2.97ms   8.67ms    323.       1.7MB
#> 3 datatable    1000   3.82ms      4ms   3.92ms   8.06ms    250.      2.78MB
#> 4 tidyr       10000  19.31ms  20.34ms  19.95ms  22.98ms     49.2     8.24MB
#> 5 reshape2    10000  13.81ms   14.4ms   14.4ms   15.6ms     69.4    11.34MB
#> 6 datatable   10000  14.56ms  15.16ms  14.91ms  18.93ms     66.0     2.98MB
#> 7 tidyr      100000 197.24ms 219.69ms 205.27ms 268.92ms      4.55   90.55MB
#> 8 reshape2   100000 164.02ms 195.32ms 176.31ms 284.77ms      5.12  121.69MB
#> 9 datatable  100000  51.31ms  60.34ms  58.36ms 113.69ms     16.6    27.36MB
#> # ... with 3 more variables: n_gc <dbl>, n_itr <int>, total_time <bch:tm>

Created on 2019-02-27 by the reprex package (v0.2.1)

like image 21
hplieninger Avatar answered Oct 19 '22 10:10

hplieninger