Icewaffle
Icewaffle

Reputation: 19

Reshape data to split column values into columns

df <- data.frame(animal = c("dog", "dog", "cat", "dog", "cat", "cat"),
                 hunger = c(0, 1, 1, 0, 1,1))

I have a dataframe like the one above with two columns, one containing categories and the other containing binary data.

I am looking to reshape the dataframe to split the category ("animal") column up into two columns of its own with the values of "animal" column as column names and the values of the other column (hunger) as cell values, i.e.

Desired output:

df <- data.frame(dog = c(0, 1, 0),
                 cat = c(1, 1, 1))

How can I achieve this?

Upvotes: 17

Views: 1153

Answers (7)

ThomasIsCoding
ThomasIsCoding

Reputation: 101034

In the case of uneven length among different categories, we can use

list2DF(
  lapply(
    . <- unstack(df, hunger ~ animal),
    `length<-`,
    max(lengths(.))
  )
)

or

list2DF(
  lapply(
    . <- unstack(rev(df)),
    `length<-`,
    max(lengths(.))
  )
)

and we will obtain

  cat dog
1   1   0
2   1   1
3   1   0
4   0  NA

Dummy data

df <- data.frame(
  animal = c("dog", "dog", "cat", "dog", "cat", "cat", "cat"),
  hunger = c(0, 1, 1, 0, 1, 1, 0)
)

We can also use unstack, e.g.,

> unstack(rev(df))
  cat dog
1   1   0
2   1   1
3   1   0

or

> unstack(df, hunger ~ animal)
  cat dog
1   1   0
2   1   1
3   1   0

Upvotes: 10

TarJae
TarJae

Reputation: 78917

Base R:

df$id <- ave(df$hunger, df$animal, FUN = seq_along)
reshape(df, idvar = "id", timevar = "animal", direction = "wide")[, -1]

  hunger.dog hunger.cat
1          0          1
2          1          1
4          0          1

Upvotes: 9

akrun
akrun

Reputation: 886938

Using data.table

library(data.table)
dcast(setDT(df), rowid(animal) ~ animal)[, animal  := NULL][]

-output

    cat dog
1:   1   0
2:   1   1
3:   1   0

Upvotes: 7

zx8754
zx8754

Reputation: 56004

Using split:

data.frame(split(df$hunger, df$animal))
#   cat dog
# 1   1   0
# 2   1   1
# 3   1   0

Upvotes: 9

Matt
Matt

Reputation: 7385

Throwing a tidyverse/purrr solution into the mix:

library(tidyverse)

df <- data.frame(animal = c("dog", "dog", "cat", "dog", "cat", "cat"),
                 hunger = c(0, 1, 1, 0, 1,1))

df %>% 
  group_split(animal) %>% 
  map(~tibble(!!quo_name(unique(.x$animal)) := .x$hunger)) %>% 
  list_cbind()
  
#> # A tibble: 3 × 2
#>     cat   dog
#>   <dbl> <dbl>
#> 1     1     0
#> 2     1     1
#> 3     1     0

Upvotes: 7

Just James
Just James

Reputation: 1252

A tidy framework way

library(dplyr)
library(tidyr)

df |> 
  pivot_wider(names_from = animal, values_from = hunger, values_fn = list) |> 
  unnest(cols = c("dog", "cat"))

Base R

do.call(cbind.data.frame, tapply(df$hunger, df$animal, `+`))

Upvotes: 7

Quinten
Quinten

Reputation: 41225

You could use pivot_wider by first creating an id for each group to identify the duplicates and use the names_from and values_from like this:

library(dplyr)
library(tidyr)
df %>%
  group_by(animal) %>%
  mutate(id = row_number()) %>%
  pivot_wider(names_from = animal, values_from = hunger) %>%
  select(-id)
#> # A tibble: 3 × 2
#>     dog   cat
#>   <dbl> <dbl>
#> 1     0     1
#> 2     1     1
#> 3     0     1

Created on 2023-03-17 with reprex v2.0.2

Upvotes: 7

Related Questions