3 votes

Trier 2 colonnes simultanément sur la base du nom du groupe

Ensemble de données

> read.delim("df.tsv")
   col1 col2 group
1     3    2    aa
2     1    1    aa
3     4    1    aa
4     4    3    aa
5     5    3    ab
6     3    2    ab
7     4    1    ab
8     2    4    ab
9     4    2    ba
10    1    4    ba
11    3    1    ba
12    4    3    ba
13    4    2    bb
14    2    3    bb
15    3    1    bb
16    1    2    bb

Je veux trier les colonnes col1 et col2 au sein de chacun des 4 groupes de la manière suivante :

  • Si le 1er dans le nom du groupe est " a ", trier col1 dans un descendant et ascendant si c'est " b "
  • Si le 2ème dans le nom du groupe est " a ", trier col2 dans un descendant et ascendant si c'est " b "
  • J'aimerais surtout que les deux colonnes soient triés simultanément Par exemple, si le groupe est "aa", le tri pour ce groupe devrait ressembler à ceci :

    col1 col2 group 1 4 3 aa 2 3 2 aa 3 4 1 aa 4 1 1 aa ...

Cela pourrait être accompli par exemple par une " une rangée à la fois "Approche d'abord col1, puis col2, en alternant pour chaque ligne.

Code et sortie actuels

library(dplyr)

read.delim("df.tsv") %>%
  group_by(group) %>%
  arrange(ifelse(substr(group, 1,1) == "a", desc(col1), col1), # if first character in group name is "a", sort col1 in a descending manner, and ascending if it's "b"
          ifelse(substr(group, 2,2) == "a", desc(col2), col2), # if second character in group name is also "a", sort also col2 in a descending manner, and ascending if it's "b"
          .by_group = TRUE)

    col1  col2 group
 1     4     3 aa   
 2     4     1 aa   
 3     3     2 aa   
 4     1     1 aa   
 5     5     3 ab   
 6     4     1 ab   
 7     3     2 ab   
 8     2     4 ab   
 9     1     4 ba   
10     3     1 ba   
11     4     3 ba   
12     4     2 ba   
13     1     2 bb   
14     2     3 bb   
15     3     1 bb   
16     4     2 bb

Cependant, cela ne ne pas remplir le 3ème critère, le " tri simultané d'une ligne à la fois ".

Sortie souhaitée

    col1  col2 group
 1     4     3 aa   
 2     3     2 aa   
 3     4     1 aa   
 4     1     1 aa   
 5     5     3 ab   
 6     4     1 ab   
 7     3     2 ab   
 8     2     4 ab   
 9     1     4 ba   
10     4     3 ba   
11     3     1 ba   
12     4     2 ba   
13     1     2 bb   
14     3     1 bb   
15     2     3 bb   
16     4     2 bb

EDITAR

Il y a quelques réponses qui font effectivement la tâche proposée, donc je pense qu'un critère de départage pourrait être que l'algorithme est flexible en ce qui concerne le nombre de colonnes à trier, par exemple 3 :

col1    col2    col3    group
3   2   4   aaa
1   1   2   aaa
4   1   4   aaa
4   3   1   aaa
5   3   3   aab
3   2   2   aab
4   1   1   aab
2   4   1   aab
4   2   3   aba
1   4   3   aba
3   1   2   aba
4   3   3   aba
3   2   4   abb
1   1   2   abb
4   1   4   abb
4   3   1   abb
4   2   1   baa
2   3   2   baa
3   1   2   baa
1   2   1   baa
5   3   3   bab
3   2   2   bab
4   1   1   bab
2   4   1   bab
4   2   3   bba
1   4   3   bba
3   1   2   bba
4   3   3   bba
4   2   1   bbb
2   3   2   bbb
3   1   2   bbb
1   2   1   bbb

La sortie devrait être

col1    col2    col3    group
4   3   1   aaa
3   2   4   aaa
4   1   4   aaa
1   1   2   aaa
5   3   3   aab
2   4   1   aab
4   1   1   aab
3   2   2   aab
4   2   3   aba
3   1   2   aba
4   3   3   aba
1   4   3   aba
4   1   4   abb
1   1   2   abb
4   3   1   abb
3   2   4   abb
1   2   1   baa
2   3   2   baa
3   1   2   baa
4   2   1   baa
2   4   1   bab
5   3   3   bab
4   1   1   bab
3   2   2   bab
1   4   3   bba
3   1   2   bba
4   2   3   bba
4   3   3   bba
1   2   1   bbb
3   1   2   bbb
4   2   1   bbb
2   3   2   bbb

Actuellement, les 2 solutions proposées ne fonctionnent pas lorsque 3 colonnes ou plus sont incluses, elles trient sur la base de 2 colonnes seulement.

EDIT 2

Si, par exemple, group=='aba', la première ligne de ce groupe doit être celle qui inclut la valeur la plus élevée dans col1 ; la 2ème ligne celle qui inclut la valeur la plus basse (restante) dans col2 ; la 3ème ligne celle qui inclut la valeur la plus élevée (restante) dans col3, et la 4ème ligne est la ligne restante. Dans ce cas, la quatrième ligne est celle qui contient la valeur la plus élevée (restante) dans la colonne 1, la cinquième ligne est celle qui contient la valeur la plus basse (restante) dans la colonne 2, etc.

Plus de détails

Exemple : Pour la 2ème ligne du groupe 'aba', dans le cas où il y a une égalité entre 2 lignes pour la valeur la plus basse (restante) dans col2, par exemple.

row-a 3 1 4 aba
row-b 2 1 4 aba

(remarquez qu'il y a un 1 en col2 dans les deux lignes), idéalement, la deuxième ligne choisie serait la ligne-a, puisque la col1 doit être triée de manière descendante ('a') dans ce groupe, et 3>2, et pour col3 4==4 de toute façon.

Si au contraire

row-a 3 1 4 aba
row-b 2 1 5 aba

laissez la priorité aller col3>col2>col1, puisque le cycle va col1>col2>col3... donc la 2ème ligne serait la ligne-b, puisque 5>4.

Donc, pour généraliser, s'il y a 5 colonnes et que le groupe est "aabaa", et qu'il y a une égalité pour choisir la 3ème ligne entre 2 lignes :

row-a 3 2 1 3 3 aabaa
row-b 5 4 1 4 2 aabaa

(col3 == 1 dans les deux), alors celle à sélectionner serait la ligne-a puisque pour col5 3>2. Si au contraire

row-a--> 3 2 1 3 3
row-b--> 5 4 1 4 3

(col5==3 dans les deux), alors choisissez la ligne-b puisque pour col4 4>3.

4voto

ekoam Points 6165

En y réfléchissant, je pense que je peux vous passer cette option. Vous pouvez maintenant spécifier la méthode de cyclage que vous voulez.

alt_order <- function(..., type, cyc) {
  cols <- unname(list(...))
  stopifnot(
    # sanity checks; you may skip if you think they are unnecessary
    length(unique(lengths(cols))) == 1L,
    length(cols) == length(type),
    all(unlist(type) %in% c(1L, -1L))
  ) 
  cols <- mapply(`*`, cols, type, SIMPLIFY = FALSE)
  out <- integer(length(cols[[1L]]))
  this <- cols
  for (i in seq_along(out)) {
    out[[i]] <- do.call(order, this)[[1L]]
    cols <- lapply(cols, `is.na<-`, out[[i]])
    this <- cols[cyc(i)]
  }
  out
}

cyc doit être une fonction qui accepte un seul entier en entrée et renvoie un vecteur d'entiers. Par exemple, si vous avez 3 colonnes et que vous voulez répliquer la fonction rev comportement cycliste comme je l'ai décrit dans le commentaire ci-dessous, vous pouvez faire ceci

mycyc <- function(i) list(1:3, 3:1)[[(i - 1) %% 2L + 1L]]
df %>% group_by(group) %>% slice(alt_order(col1, col2, col3, type = ab2sign(group), cyc = mycyc))

Eh bien, peut-être qu'une solution peu efficace mais simple consiste à trier les deux colonnes de façon continue, en échangeant la colonne principale à chaque fois, et en déchargeant le premier élément jusqu'à ce qu'il ne reste plus aucun élément à trier. Voici la fonction.

alt_order <- function(..., type) {
  cols <- unname(list(...))
  stopifnot(
    # sanity checks; you may skip if you think they are unnecessary
    length(unique(lengths(cols))) == 1L,
    length(cols) == length(type),
    all(unlist(type) %in% c(1L, -1L))
  ) 
  cols <- mapply(`*`, cols, type, SIMPLIFY = FALSE)
  out <- integer(length(cols[[1L]]))
  for (i in seq_along(out)) {
    out[[i]] <- do.call(order, cols)[[1L]]
    cols <- rev(lapply(cols, `is.na<-`, out[[i]]))
  }
  out
}

Nous attribuons des valeurs à NA pour les décharger puisque NA seront triés jusqu'au dernier, de manière ascendante. type doit être soit 1 soit -1 et est utilisé pour rationaliser l'ordre que nous souhaitons imposer puisque l'ordre décroissant de c(1,2,3) est le même que l'ordre ascendant de -1 * c(1,2,3) . Nous avons également besoin d'une fonction d'aide comme suit pour transférer votre group en 1 et -1

ab2sign <- function(x) {
  out <- data.table::transpose(strsplit(x, "", fixed = TRUE))
  lapply(out, function(x) 2L * (x == "b") - 1L)
}

Nous pouvons maintenant les appliquer

df %>% group_by(group) %>% slice(alt_order(col1, col2, type = ab2sign(group)))

Sortie

# A tibble: 16 x 3
# Groups:   group [4]
    col1  col2 group
   <int> <int> <chr>
 1     4     3 aa   
 2     3     2 aa   
 3     4     1 aa   
 4     1     1 aa   
 5     5     3 ab   
 6     4     1 ab   
 7     3     2 ab   
 8     2     4 ab   
 9     1     4 ba   
10     4     3 ba   
11     3     1 ba   
12     4     2 ba   
13     1     2 bb   
14     3     1 bb   
15     2     3 bb   
16     4     2 bb

J'espère voir des solutions plus efficaces (peut-être vectorisées) avant que la prime n'expire.

2voto

ThomasIsCoding Points 54213

Mise à jour

Voici une option qui peut fonctionner dans des cas généraux, c'est-à-dire plus de 2 colonnes :

f <- function(.) {
  col <- .[-length(.)] * (2 * (t(list2DF(strsplit(.$group, ""))) == "b") - 1)
  r <- data.frame()
  while (nrow(.)) {
    p <- do.call(order, col[(seq_along(col) + nrow(r) - 1) %% length(col) + 1])[1]
    r <- rbind(r, .[p, ])
    col <- col[-p, ]
    . <- .[-p, ]
  }
  r
}

df %>%
  group_by(group) %>%
  do(f(.)) %>%
  ungroup()

ce qui donne

    col1  col2  col3 group
   <int> <int> <int> <chr>
 1     4     3     1 aaa
 2     3     2     4 aaa
 3     4     1     4 aaa
 4     1     1     2 aaa
 5     5     3     3 aab
 6     2     4     1 aab
 7     4     1     1 aab
 8     3     2     2 aab
 9     4     2     3 aba
10     3     1     2 aba
# ... with 22 more rows

Voici une option utilisant la programmation dynamique (mais peut-être pas aussi efficace)

f <- function(.) {
  col <- with(., data.frame(col1, col2) * (2 * (t(list2DF(strsplit(.$group, ""))) == "b") - 1))
  r <- data.frame()
  while (nrow(.)) {
    p <- do.call(order, ifelse(nrow(r) %% 2, rev, I)(col))[1]
    r <- rbind(r, .[p, ])
    col <- col[-p,]
    . <- .[-p, ]
  }
  r
}

df %>%
  group_by(group) %>%
  do(f(.)) %>%
  ungroup()

ce qui donne

# A tibble: 16 x 3
    col1  col2 group
   <int> <int> <chr>
 1     4     3 aa
 2     3     2 aa
 3     4     1 aa
 4     1     1 aa
 5     5     3 ab
 6     4     1 ab
 7     3     2 ab
 8     2     4 ab
 9     1     4 ba
10     4     3 ba
11     3     1 ba
12     4     2 ba
13     1     2 bb
14     3     1 bb
15     2     3 bb
16     4     2 bb

-2voto

jumper Points 5

Je peux vous dire la réponse, mais je ne peux pas écrire complètement r code pour cela, puisque je ne sais pas r J'espère que quelqu'un pourra modifier mon code pour une réponse complète.

supposons que les deux sortes sont ascendantes (vous pouvez généraliser à votre cas)

idx1=order(col1)
idx2=order(col2[idx1])

return col1[idx1[idx2]], col2[idx1[idx2]]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X