Spaceship222
Spaceship222

Reputation: 849

Group data by factor level, then transform to data frame with colname being levels?

There is my problem that I can't solve it:

Data:

df <- data.frame(f1=c("a", "a", "b", "b", "c", "c", "c"), 
                 v1=c(10, 11, 4, 5, 0, 1, 2))

data.frame:f1 is factor
  f1 v1
  a   10
  a   11
  b   4
  b   5
  c   0
  c   1   
  c   2
 # What I want is:(for example, fetch data with the number of element of some level == 2, then to data.frame)
  a   b
 10   4
 11   5  

Thanks in advance!

Upvotes: 0

Views: 74

Answers (5)

moodymudskipper
moodymudskipper

Reputation: 47320

Here is a solution using unstack :

unstack(
  droplevels(df[ave(df$v1, df$f1, FUN = function(x) length(x) == 2)==1,]),
  v1 ~ f1)
#    a b
# 1 10 4
# 2 11 5

A variant, similar to @thelatemail's solution :

data.frame(Filter(function(x) length(x) == 2, unstack(df,v1 ~ f1)))

My tidyverse solution would be:

library(tidyverse)
df                  %>%
  group_by(f1)      %>%
  filter(n() == 2)  %>%
  mutate(i = row_number()) %>%
  spread(f1, v1)   %>%
  select(-i)
# # A tibble: 2 x 2
#       a     b
# * <dbl> <dbl>
# 1    10     4
# 2    11     5

or mixing approaches :

as_tibble(keep(unstack(df,v1 ~ f1), ~length(.x) == 2))

Upvotes: 1

KyChen
KyChen

Reputation: 59

I'd like code this, may it helps for you

library(reshape2)

library(dplyr)

aa = data.frame(v1=c('a','a','b','b','c','c','c'),f1=c(10,11,4,5,0,1,2))

cc = aa %>% group_by(v1) %>% summarise(id = length((v1))) 

dd= merge(aa,cc) #get the level 

ee = dd[dd$aa==2,] #select number of level equal to 2

ee$id = rep(c(1,2),nrow(ee)/2) # reset index like (1,2,1,2)

dcast(ee, id~v1,value.var = 'f1')

all done!

Upvotes: 0

thelatemail
thelatemail

Reputation: 93813

split might be useful here to split df$v1 into parts corresponding to df$f1. Since you are always extracting equal length chunks, it can then simply be combined back to a data.frame:

spl <- split(df$v1, df$f1)
data.frame(spl[lengths(spl)==2])

#   a b
#1 10 4
#2 11 5

Or do it all in one call by combining this with Filter:

data.frame(Filter(function(x) length(x)==2, split(df$v1, df$f1)))
#   a b
#1 10 4
#2 11 5

Upvotes: 2

Patrick Rutz
Patrick Rutz

Reputation: 71

Using all base functions (but you should use tidyverse)

# Add count of instances
x$len <- ave(x$v1, x$f1, FUN = length)

# Filter, drop the count
x <- x[x$len==2, c('f1','v1')]

# Hacky pivot
result <- data.frame(
lapply(unique(x$f1), FUN = function(y) x$v1[x$f1==y])
)
colnames(result) <- unique(x$f1)

> result
   a b
1 10 4
2 11 5

Upvotes: 0

Ronak Shah
Ronak Shah

Reputation: 388982

I might be missing something simple here , but the below approach using dplyr works.

library(dplyr)
nlevels = 2

df1 <- df %>%
        add_count(f1) %>%
        filter(n == nlevels) %>%
        select(-n) %>%
        mutate(rn = row_number()) %>%
        spread(f1, v1) %>%
        select(-rn)

This gives

#      a     b
#   <int> <int>
#1    10    NA
#2    11    NA
#3    NA     4
#4    NA     5

Now, if you want to remove NA's we can do

do.call("cbind.data.frame", lapply(df1, function(x) x[!is.na(x)]))

#   a b
#1 10 4
#2 11 5

As we have filtered the dataframe which has only nlevels observations, we would have same number of rows for each column in the final dataframe.

Upvotes: 2

Related Questions