Combine two columns containing lists into single column based on condition

Viewed 287

I have two columns x and y in data frame which are in form of list. Some lists in col x are having logical(0) value and I want to fill these with list values from y column. How can I do that in R. enter image description here

Sample dput

df <- structure(
  list(
    x = list(
      structure(logical(0), .Dim = c(0L,
                                     2L)),
      structure(
        c(72.8468555473385, 19.1207531432888),
        .Dim = 1:2,
        .Dimnames = list("1", c("X", "Y"))
      ),
      structure(logical(0), .Dim = c(0L, 2L)),
      structure(
        c(72.8466089689375, 19.1222313526198),
        .Dim = 1:2,
        .Dimnames = list("1", c("X", "Y"))
      ),
      structure(
        c(72.8458211528575, 19.1206957620104),
        .Dim = 1:2,
        .Dimnames = list("1", c("X", "Y"))
      )
    ),
    y = list(
      structure(
        c(72.846989997634, 19.1197250026469),
        .Dim = 1:2,
        .Dimnames = list(NULL, c("lon", "lat"))
      ),
      structure(
        c(72.846989997634,
          19.1197250026469),
        .Dim = 1:2,
        .Dimnames = list(NULL, c("lon",
                                 "lat"))
      ),
      structure(
        c(72.8480650003086, 19.1195200000195),
        .Dim = 1:2,
        .Dimnames = list(NULL, c("lon", "lat"))
      ),
      structure(
        c(72.8463200059764,
          19.1207150074423),
        .Dim = 1:2,
        .Dimnames = list(NULL, c("lon",
                                 "lat"))
      ),
      structure(
        c(72.8468350022863, 19.1204500035408),
        .Dim = 1:2,
        .Dimnames = list(NULL, c("lon", "lat"))
      )
    )
  ),
  .Names = c("x", "y"),
  row.names = c(NA,-5L),
  class = "data.frame"
)

I want x col to have combined values from x and y like below

 x
    1 72.84699, 19.11973
    2 72.84686, 19.12075
    3  72.84807, 19.11952
    4 72.84661, 19.12223 
    5 72.84582, 19.12070 
2 Answers

There might be a smarter way to do this but using base R mapply we can check for length of x column and if it is less than 1 then we replace it with value in y column.

df$x <- mapply(function(x, y) if (length(x) > 1) list(x) else list(y), df$x, df$y)

df
#                   x                  y
#1 72.84699, 19.11973 72.84699, 19.11973
#2 72.84686, 19.12075 72.84699, 19.11973
#3 72.84807, 19.11952 72.84807, 19.11952
#4 72.84661, 19.12223 72.84632, 19.12072
#5 72.84582, 19.12070 72.84684, 19.12045

We can do this in a vectorized way in base R by creating a logical index to assign the values in 'x' column

i1 <- !lengths(df$x)
df$x[i1] <- df$y[i1]

Or in a single line

df$x <- replace(df$x, i1, df$y[i1])
df 
#                x                  y
#1 72.84699, 19.11973 72.84699, 19.11973
#2 72.84686, 19.12075 72.84699, 19.11973
#3 72.84807, 19.11952 72.84807, 19.11952
#4 72.84661, 19.12223 72.84632, 19.12072
#5 72.84582, 19.12070 72.84684, 19.12045

Or with tidyverse

library(tidyverse)
df %>%
    mutate(x = ifelse(lengths(x)==0, y, x))

Benchmarks

Some benchmarks on a slightly big dataset

df1 <- df[rep(seq_len(nrow(df)), 1e6), ]
df2 <- copy(df1)
system.time({
df1$x <- mapply(function(x, y) if (length(x) > 1) list(x) else list(y), df1$x, df1$y)

})
#user  system elapsed 
#  6.261   0.941   7.164 

system.time({
i1 <- !lengths(df2$x)
df2$x[i1] <- df2$y[i1]

})
# user  system elapsed 
#  0.858   0.018   0.874 
Related