JontroPothon
JontroPothon

Reputation: 600

Rearrage string elements by the number attached with it in rows

I have this data frame,

set.seed(124)
id <- rnorm(5, mean = 100, sd = 59)
charVar <- c("Eeny (2), meeny (10), miny (21), moe (1)",
  "Catch (112), a (2), tiger (33), by (44), the (2), toe (24)",
  NA,
  "If (2), he (33), hollers (15), let (66), him (1), go (55)",
  "Eeny (224), meeny (44), miny (50), moe (76)")
df <- data.frame(id, charVar)
df
> df
         id                                                    charVar
1  18.28083                   Eeny (2), meeny (10), miny (21), moe (1)
2 102.26107 Catch (112), a (2), tiger (33), by (44), the (2), toe (24)
3  54.98122                                                       <NA>
4 112.52606  If (2), he (33), hollers (15), let (66), him (1), go (55)
5 184.10674                Eeny (224), meeny (44), miny (50), moe (76)

I want to sort every element in the rows by the numbers with it. The expected output should look like this:

> df
         id                                                     charVar
1  18.28083                    miny (21), meeny (10), Eeny (2), moe (1)
2 102.26107 Catch (112), by (44), tiger (33), toe (24), a (2),  the (2)
3  54.98122                                                        <NA>
4 112.52606   let (66), go (55), he (33), hollers (15), If (2), him (1)
5 184.10674                 Eeny (224), moe (76), miny (50), meeny (44)

Any idea how to achive the expected result? Any help would be greatly appreciated.

Upvotes: 2

Views: 90

Answers (3)

ThomasIsCoding
ThomasIsCoding

Reputation: 102529

With base R, you can try

transform(
    df,
    charVar = sapply(
        strsplit(charVar, ", "),
        \(x) toString(x[order(-as.integer(gsub("\\D+", "", x)))])
    )
)

or you can use gtools::mixedsort for simpler implementation

library(gtools)
df %>%
    mutate(charVar = sapply(
        strsplit(charVar, ", "),
        \(x) toString(mixedsort(x, decreasing = TRUE))
    ))

which gives

         id                                                    charVar
1  18.28083                   miny (21), meeny (10), Eeny (2), moe (1)
2 102.26107 Catch (112), by (44), tiger (33), toe (24), a (2), the (2)
3  54.98122                                                         NA
4 112.52606  let (66), go (55), he (33), hollers (15), If (2), him (1)
5 184.10674                Eeny (224), moe (76), miny (50), meeny (44)

Upvotes: 1

SamR
SamR

Reputation: 20494

As you've tagged and here are approaches using both.

tidyverse approach

Essentially we strsplit() charVar into a list-column where each element is a character vector, tidyr::unnest() into long form, extract the numbers, then dplyr::summarise() back into one row per id, where we paste() back together the values in decreasing order():

library(dplyr)
df |>
    mutate(charVar = strsplit(charVar, ", ")) |>
    tidyr::unnest(charVar) |>
    mutate(n = as.integer(gsub("\\D+", "", charVar))) |>
    summarise(
        charVar = paste(charVar[order(-n)], collapse = ", "),
        .by = id
    )


#          id                                                    charVar
# 1  18.28083                   miny (21), meeny (10), Eeny (2), moe (1)
# 2 102.26107 Catch (112), by (44), tiger (33), toe (24), a (2), the (2)
# 3  54.98122                                                         NA
# 4 112.52606  let (66), go (55), he (33), hollers (15), If (2), him (1)
# 5 184.10674                Eeny (224), moe (76), miny (50), meeny (44)

data.table approach

There is no equivalent of tidyr::unnest(). While the same results can be achieved with unlisting, here's an approach which feels more idiomatic, which modifies charVar in place:

library(data.table)
setDT(df)

df[, charVar := lapply(charVar, \(x) {
    parts <- unlist(strsplit(x, ", "))
    n <- as.integer(gsub("\\D+", "", parts))
    paste(parts[order(-n)], collapse = ", ")
})]

#           id                                                    charVar
#        <num>                                                     <list>
# 1:  18.28083                   miny (21), meeny (10), Eeny (2), moe (1)
# 2: 102.26107 Catch (112), by (44), tiger (33), toe (24), a (2), the (2)
# 3:  54.98122                                                         NA
# 4: 112.52606  let (66), go (55), he (33), hollers (15), If (2), him (1)
# 5: 184.10674                Eeny (224), moe (76), miny (50), meeny (44)

Upvotes: 4

zx8754
zx8754

Reputation: 56219

Looks like this is a follow up to your previous question. Instead of dealing with XY problem, avoid the problem from the start:

#example data
df <- data.frame(var_1 = c(10, 5, 6, 0),
                 var_2 = c(0, 0, 3, 0),
                 var_3 = c(2, 0, 9, 0))

#sort and collapse to string 
df$resString <- apply(df, 1, function(x, y = x[ x != 0 ]){
  ix <- order(y, decreasing = TRUE)
  if(length(ix)) paste(paste0(names(y)[ ix ], " (", y[ ix ], ")"), collapse = ", ")  else return(NA)
  })

#sort and keep as list column
df$resList <- apply(df[, grep("^var", colnames(df), value = TRUE) ], 1,
                    function(i) sort(i[ i != 0 ], decreasing = TRUE))

#df
#   var_1 var_2 var_3                       resString resList
# 1    10     0     2           var_1 (10), var_3 (2)   10, 2
# 2     5     0     0                       var_1 (5)       5
# 3     6     3     9 var_3 (9), var_1 (6), var_2 (3) 9, 6, 3
# 4     0     0     0                            <NA>   

Upvotes: 6

Related Questions