jay.sf
jay.sf

Reputation: 73272

How to bind rows without losing those with character(0)?

I have a list like L (comes from a vector splitting).

L <- strsplit(c("1 5 9", "", "3 7 11", ""), " ")

# [[1]]
# [1] "1" "5" "9"
# 
# [[2]]
# character(0)
# 
# [[3]]
# [1] "3"  "7"  "11"
# 
# [[4]]
# character(0)

When I do an ordinary rbind as follows, I'm losing all the character(0) rows.

do.call(rbind, L)
#      [,1] [,2] [,3]
# [1,] "1"  "5"  "9" 
# [2,] "3"  "7"  "11"

Do I always have to do a lapply like the following or have I missed something?

do.call(rbind, lapply(L, function(x) 
    if (length(x) == 0)  rep("", 3) else x))
#      [,1] [,2] [,3]
# [1,] "1"  "5"  "9" 
# [2,] ""   ""   ""  
# [3,] "3"  "7"  "11"
# [4,] ""   ""   ""  

Base R answers are preferred.

Upvotes: 5

Views: 360

Answers (5)

akrun
akrun

Reputation: 887501

We can use stri_list2matrix in a simple way

library(stringi)
stri_list2matrix(L, byrow = TRUE, fill = "")
#   [,1] [,2] [,3]
#[1,] "1"  "5"  "9" 
#[2,] ""   ""   ""  
#[3,] "3"  "7"  "11"
#[4,] ""   ""   ""  

Upvotes: 2

Ronak Shah
Ronak Shah

Reputation: 389135

If you use lapply you don't have to worry about length so you can skip the rep part it will automatically be recycled across columns.

do.call(rbind, lapply(L, function(x) if (length(x) == 0)  "" else x))

#    [,1] [,2] [,3]
#[1,] "1"  "5"  "9" 
#[2,] ""   ""   ""  
#[3,] "3"  "7"  "11"
#[4,] ""   ""   ""  

Another option using same logic as @NelsonGon we can replace the empty lists with blank and then rbind.

L[lengths(L) == 0] <- ""
do.call(rbind, L)

#    [,1] [,2] [,3]
#[1,] "1"  "5"  "9" 
#[2,] ""   ""   ""  
#[3,] "3"  "7"  "11"
#[4,] ""   ""   ""  

Upvotes: 3

tmfmnk
tmfmnk

Reputation: 40121

That is the defined behavior for scenarios like that. As written in ?rbind:

For cbind (rbind), vectors of zero length (including NULL) are ignored unless the result would have zero rows (columns), for S compatibility. (Zero-extent matrices do not occur in S3 and are not ignored in R.)

When you inspect your elements, you see that it is true:

length(L[[1]])

[1] 3

length(L[[2]])

[1] 0

However, as you see, multiple workarounds are possible.

Upvotes: 2

NelsonGon
NelsonGon

Reputation: 13319

With plyr then proceed with replacement. Since OP asked for base R, see below.

 plyr::ldply(L,rbind)
     1    2    3
1    1    5    9
2 <NA> <NA> <NA>
3    3    7   11
4 <NA> <NA> <NA>

A less efficient base R way:

 L <- strsplit(c("1 5 9", "", "3 7 11", ""), " ")
 L[lapply(L,length)==0]<-"Miss"
 res<-Reduce(rbind,L)
 res[res=="Miss"]<-""

Result:

     [,1] [,2] [,3]
init "1"  "5"  "9" 
     ""   ""   ""  
     "3"  "7"  "11"
     ""   ""   ""  

Upvotes: 2

LocoGris
LocoGris

Reputation: 4480

Maybe this roundabout using data.table suits you:

L <- data.table::tstrsplit(c("1 5 9", "", "3 7 11", ""), " ", fill="")
t(do.call(rbind,L))

Upvotes: 2

Related Questions