Fractionner un vecteur en morceaux dans R


227

Je dois diviser un vecteur en n morceaux de taille égale dans R. Je n'ai trouvé aucune fonction de base pour le faire. De plus, Google ne m'a conduit nulle part. Voici donc ce que j'ai trouvé, j'espère que cela aidera quelqu'un quelque part.

x <- 1:10
n <- 3
chunk <- function(x,n) split(x, factor(sort(rank(x)%%n)))
chunk(x,n)
$`0`
[1] 1 2 3

$`1`
[1] 4 5 6 7

$`2`
[1]  8  9 10

Tous les commentaires, suggestions ou améliorations sont vraiment les bienvenus et appréciés.

À la vôtre, Sebastian


5
Oui, il n'est pas certain que ce que vous obtenez soit la solution à "n morceaux de taille égale". Mais peut-être que cela vous y mène aussi: x <- 1:10; n <- 3; split (x, cut (x, n, labels = FALSE))
mdsumner

la solution dans la question et la solution dans le commentaire précédent sont incorrectes, en ce sens qu'elles peuvent ne pas fonctionner si le vecteur a des entrées répétées. Essayez ceci:> foo <- c (rep (1, 12), rep (2,3), rep (3,3)) [1] 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 3 3 3> chunk (foo, 2) (donne un mauvais résultat)> chunk (foo, 3) (également faux)
mathheadinclouds

(suite du commentaire précédent) pourquoi? rang (x) n'a pas besoin d'être un entier> rang (c (1,1,2,3)) [1] 1,5 1,5 3,0 4,0 c'est pourquoi la méthode de la question échoue. celui-ci fonctionne (merci à Harlan ci-dessous)> chunk2 <- fonction (x, n) split (x, cut (seq_along (x), n, labels = FALSE))
mathheadinclouds

2
> split (foo, cut (foo, 3, labels = FALSE)) (également faux)
mathheadinclouds

1
Comme le suggère @mathheadinclouds, l'exemple de données est un cas très spécial. Des exemples plus généraux seraient des tests plus utiles et meilleurs. Par exemple, x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)donne des exemples avec des données manquantes, des valeurs répétées, qui ne sont pas déjà triées et qui sont dans différentes classes (entier, caractère, facteur).
Kalin

Réponses:


314

Un doublure divisant d en morceaux de taille 20:

split(d, ceiling(seq_along(d)/20))

Plus de détails: je pense que tout ce dont vous avez besoin est seq_along(), split()etceiling() :

> d <- rpois(73,5)
> d
 [1]  3  1 11  4  1  2  3  2  4 10 10  2  7  4  6  6  2  1  1  2  3  8  3 10  7  4
[27]  3  4  4  1  1  7  2  4  6  0  5  7  4  6  8  4  7 12  4  6  8  4  2  7  6  5
[53]  4  5  4  5  5  8  7  7  7  6  2  4  3  3  8 11  6  6  1  8  4
> max <- 20
> x <- seq_along(d)
> d1 <- split(d, ceiling(x/max))
> d1
$`1`
 [1]  3  1 11  4  1  2  3  2  4 10 10  2  7  4  6  6  2  1  1  2

$`2`
 [1]  3  8  3 10  7  4  3  4  4  1  1  7  2  4  6  0  5  7  4  6

$`3`
 [1]  8  4  7 12  4  6  8  4  2  7  6  5  4  5  4  5  5  8  7  7

$`4`
 [1]  7  6  2  4  3  3  8 11  6  6  1  8  4

34
La question demande des nmorceaux de taille égale. Cela vous donne un nombre inconnu de morceaux de taille n. J'ai eu le même problème et j'ai utilisé les solutions de @mathheadinclouds.
rrs

4
Comme on peut le voir à la sortie de d1, cette réponse ne divise pas d en groupes de taille égale (4 est évidemment plus court). Il ne répond donc pas à la question.
Calimo

9
@rrs: split (d, plafond (seq_along (d) / (longueur (d) / n)))
gkcn

Je sais que c'est assez ancien, mais cela peut être utile à ceux qui trébuchent ici. Bien que la question de l'OP était de se diviser en morceaux de taille égale, si le vecteur n'est pas un multiple du diviseur, le dernier morceau aura une taille différente de celle du morceau. Pour diviser en n-chunksj'ai utilisé max <- length(d)%/%n. Je l'ai utilisé avec un vecteur de 31 chaînes et j'ai obtenu une liste de 3 vecteurs de 10 phrases et un de 1 phrase.
salvu


36
simplified version...
n = 3
split(x, sort(x%%n))

J'aime cela car il vous donne des morceaux de taille aussi égale que possible (bon pour diviser une tâche volumineuse, par exemple pour accueillir une mémoire RAM limitée ou pour exécuter une tâche sur plusieurs threads).
alexvpickering

3
C'est utile, mais gardez à l'esprit que cela ne fonctionnera que sur les vecteurs numériques.
Keith Hughitt

@KeithHughitt cela peut être résolu avec des facteurs et en retournant les niveaux sous forme numérique. Ou du moins, c'est comme ça que je l'ai implémenté.
drmariod

20

Essayez la fonction ggplot2, cut_number:

library(ggplot2)
x <- 1:10
n <- 3
cut_number(x, n) # labels = FALSE if you just want an integer result
#>  [1] [1,4]  [1,4]  [1,4]  [1,4]  (4,7]  (4,7]  (4,7]  (7,10] (7,10] (7,10]
#> Levels: [1,4] (4,7] (7,10]

# if you want it split into a list:
split(x, cut_number(x, n))
#> $`[1,4]`
#> [1] 1 2 3 4
#> 
#> $`(4,7]`
#> [1] 5 6 7
#> 
#> $`(7,10]`
#> [1]  8  9 10

2
Cela ne fonctionne pas pour le fractionnement du x, you zdéfini dans ce commentaire . En particulier, il trie les résultats, qui peuvent être corrects ou non, selon l'application.
Kalin

Au contraire, ce commentaire .
Kalin

18

Cela le divisera différemment de ce que vous avez, mais c'est toujours une belle structure de liste, je pense:

chunk.2 <- function(x, n, force.number.of.groups = TRUE, len = length(x), groups = trunc(len/n), overflow = len%%n) { 
  if(force.number.of.groups) {
    f1 <- as.character(sort(rep(1:n, groups)))
    f <- as.character(c(f1, rep(n, overflow)))
  } else {
    f1 <- as.character(sort(rep(1:groups, n)))
    f <- as.character(c(f1, rep("overflow", overflow)))
  }

  g <- split(x, f)

  if(force.number.of.groups) {
    g.names <- names(g)
    g.names.ordered <- as.character(sort(as.numeric(g.names)))
  } else {
    g.names <- names(g[-length(g)])
    g.names.ordered <- as.character(sort(as.numeric(g.names)))
    g.names.ordered <- c(g.names.ordered, "overflow")
  }

  return(g[g.names.ordered])
}

Ce qui vous donnera les éléments suivants, selon la façon dont vous souhaitez le mettre en forme:

> x <- 1:10; n <- 3
> chunk.2(x, n, force.number.of.groups = FALSE)
$`1`
[1] 1 2 3

$`2`
[1] 4 5 6

$`3`
[1] 7 8 9

$overflow
[1] 10

> chunk.2(x, n, force.number.of.groups = TRUE)
$`1`
[1] 1 2 3

$`2`
[1] 4 5 6

$`3`
[1]  7  8  9 10

Exécution de deux synchronisations à l'aide de ces paramètres:

set.seed(42)
x <- rnorm(1:1e7)
n <- 3

Ensuite, nous avons les résultats suivants:

> system.time(chunk(x, n)) # your function 
   user  system elapsed 
 29.500   0.620  30.125 

> system.time(chunk.2(x, n, force.number.of.groups = TRUE))
   user  system elapsed 
  5.360   0.300   5.663 

EDIT: Le passage de as.factor () à as.character () dans ma fonction l'a rendu deux fois plus rapide.


13

Quelques variantes de plus à la pile ...

> x <- 1:10
> n <- 3

Notez que vous n'avez pas besoin d'utiliser la factorfonction ici, mais que vous souhaitez toujours afficher sortvotre premier vecteur 1 2 3 10:

> chunk <- function(x, n) split(x, sort(rank(x) %% n))
> chunk(x,n)
$`0`
[1] 1 2 3
$`1`
[1] 4 5 6 7
$`2`
[1]  8  9 10

Ou vous pouvez attribuer des indices de caractères, et non les nombres dans les cases à gauche ci-dessus:

> my.chunk <- function(x, n) split(x, sort(rep(letters[1:n], each=n, len=length(x))))
> my.chunk(x, n)
$a
[1] 1 2 3 4
$b
[1] 5 6 7
$c
[1]  8  9 10

Ou vous pouvez utiliser des noms en mots simples stockés dans un vecteur. Notez que l'utilisation sortpour obtenir des valeurs consécutives en xordre alphabétique les étiquettes:

> my.other.chunk <- function(x, n) split(x, sort(rep(c("tom", "dick", "harry"), each=n, len=length(x))))
> my.other.chunk(x, n)
$dick
[1] 1 2 3
$harry
[1] 4 5 6
$tom
[1]  7  8  9 10

12

Utilisation des R de base rep_len:

x <- 1:10
n <- 3

split(x, rep_len(1:n, length(x)))
# $`1`
# [1]  1  4  7 10
# 
# $`2`
# [1] 2 5 8
# 
# $`3`
# [1] 3 6 9

Et comme déjà mentionné si vous voulez des indices triés, simplement:

split(x, sort(rep_len(1:n, length(x))))
# $`1`
# [1] 1 2 3 4
# 
# $`2`
# [1] 5 6 7
# 
# $`3`
# [1]  8  9 10

9

Vous pouvez combiner le split / cut, comme suggéré par mdsummer, avec le quantile pour créer des groupes pairs:

split(x,cut(x,quantile(x,(0:n)/n), include.lowest=TRUE, labels=FALSE))

Cela donne le même résultat pour votre exemple, mais pas pour les variables asymétriques.


7

split(x,matrix(1:n,n,length(x))[1:length(x)])

c'est peut-être plus clair, mais la même idée:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

si vous voulez qu'il soit commandé, jetez-en une sorte


6

J'avais besoin de la même fonction et j'ai lu les solutions précédentes, mais j'avais aussi besoin d'avoir le morceau déséquilibré pour être à la fin, c'est-à-dire si j'ai 10 éléments pour les diviser en vecteurs de 3 chacun, alors mon résultat devrait avoir des vecteurs avec 3, 3,4 éléments respectivement. J'ai donc utilisé ce qui suit (j'ai laissé le code non optimisé pour la lisibilité, sinon pas besoin d'avoir beaucoup de variables):

chunk <- function(x,n){
  numOfVectors <- floor(length(x)/n)
  elementsPerVector <- c(rep(n,numOfVectors-1),n+length(x) %% n)
  elemDistPerVector <- rep(1:numOfVectors,elementsPerVector)
  split(x,factor(elemDistPerVector))
}
set.seed(1)
x <- rnorm(10)
n <- 3
chunk(x,n)
$`1`
[1] -0.6264538  0.1836433 -0.8356286

$`2`
[1]  1.5952808  0.3295078 -0.8204684

$`3`
[1]  0.4874291  0.7383247  0.5757814 -0.3053884

6

Voici une autre variante.

REMARQUE: avec cet exemple, vous spécifiez la TAILLE DU MORCEAU dans le deuxième paramètre

  1. tous les morceaux sont uniformes, sauf le dernier;
  2. le dernier sera au pire plus petit, jamais plus grand que la taille du morceau.

chunk <- function(x,n)
{
    f <- sort(rep(1:(trunc(length(x)/n)+1),n))[1:length(x)]
    return(split(x,f))
}

#Test
n<-c(1,2,3,4,5,6,7,8,9,10,11)

c<-chunk(n,5)

q<-lapply(c, function(r) cat(r,sep=",",collapse="|") )
#output
1,2,3,4,5,|6,7,8,9,10,|11,|

4

Fonction simple pour diviser un vecteur en utilisant simplement des index - pas besoin de trop compliquer cela

vsplit <- function(v, n) {
    l = length(v)
    r = l/n
    return(lapply(1:n, function(i) {
        s = max(1, round(r*(i-1))+1)
        e = min(l, round(r*i))
        return(v[s:e])
    }))
}

3

Si vous n'aimez pas split() et que vous n'aimez pas matrix()(avec ses NA pendantes), il y a ceci:

chunk <- function(x, n) (mapply(function(a, b) (x[a:b]), seq.int(from=1, to=length(x), by=n), pmin(seq.int(from=1, to=length(x), by=n)+(n-1), length(x)), SIMPLIFY=FALSE))

Comme split(), il renvoie une liste, mais il ne perd pas de temps ou d'espace avec des étiquettes, il peut donc être plus performant.


2

Nous remercions @Sebastian pour cette fonction

chunk <- function(x,y){
         split(x, factor(sort(rank(row.names(x))%%y)))
         }

2

Si vous n'aimez pas split()et que cela ne vous dérange pas que les AN remplissent votre queue courte:

chunk <- function(x, n) { if((length(x)%%n)==0) {return(matrix(x, nrow=n))} else {return(matrix(append(x, rep(NA, n-(length(x)%%n))), nrow=n))} }

Les colonnes de la matrice retournée ([, 1: ncol]) sont les droïdes que vous recherchez.


2

J'ai besoin d'une fonction qui prend l'argument d'un data.table (entre guillemets) et un autre argument qui est la limite supérieure du nombre de lignes dans les sous-ensembles de ce data.table d'origine. Cette fonction produit le nombre de tableaux de données que cette limite supérieure autorise:

library(data.table)    
split_dt <- function(x,y) 
    {
    for(i in seq(from=1,to=nrow(get(x)),by=y)) 
        {df_ <<- get(x)[i:(i + y)];
            assign(paste0("df_",i),df_,inherits=TRUE)}
    rm(df_,inherits=TRUE)
    }

Cette fonction me donne une série de data.tables nommée df_ [numéro] avec la ligne de départ du data.table d'origine dans le nom. La dernière table data.table peut être courte et remplie de NA, vous devez donc la redéfinir sur les données restantes. Ce type de fonction est utile car certains logiciels SIG ont des limites sur le nombre de broches d'adresse que vous pouvez importer, par exemple. Par conséquent, le découpage de data.tables en morceaux plus petits peut ne pas être recommandé, mais il peut ne pas être évitable.


2

Désolé si cette réponse arrive si tard, mais peut-être qu'elle peut être utile à quelqu'un d'autre. En fait, il existe une solution très utile à ce problème, expliquée à la fin de? Split.

> testVector <- c(1:10) #I want to divide it into 5 parts
> VectorList <- split(testVector, 1:5)
> VectorList
$`1`
[1] 1 6

$`2`
[1] 2 7

$`3`
[1] 3 8

$`4`
[1] 4 9

$`5`
[1]  5 10

3
cela se cassera s'il y a un nombre inégal de valeurs dans chaque groupe!
Matifou

2

Encore une autre possibilité est la splitIndicesfonction du package parallel:

library(parallel)
splitIndices(20, 3)

Donne:

[[1]]
[1] 1 2 3 4 5 6 7

[[2]]
[1]  8  9 10 11 12 13

[[3]]
[1] 14 15 16 17 18 19 20

0

Wow, cette question a obtenu plus de traction que prévu.

Merci pour toutes les idees. J'ai trouvé cette solution:

require(magrittr)
create.chunks <- function(x, elements.per.chunk){
    # plain R version
    # split(x, rep(seq_along(x), each = elements.per.chunk)[seq_along(x)])
    # magrittr version - because that's what people use now
    x %>% seq_along %>% rep(., each = elements.per.chunk) %>% extract(seq_along(x)) %>% split(x, .) 
}
create.chunks(letters[1:10], 3)
$`1`
[1] "a" "b" "c"

$`2`
[1] "d" "e" "f"

$`3`
[1] "g" "h" "i"

$`4`
[1] "j"

La clé consiste à utiliser le paramètre seq (each = chunk.size) afin de le faire fonctionner. L'utilisation de seq_along agit comme rank (x) dans ma solution précédente, mais est en fait capable de produire le résultat correct avec des entrées en double.


Pour ceux qui craignent que rep (seq_along (x), each = elements.per.chunk) soit trop contraignant sur la mémoire: oui c'est le cas. Vous pouvez essayer une version modifiée de ma suggestion précédente: chunk <- function (x, n) split (x, factor (seq_along (x) %% n))
Sebastian

0

Cela se divise en morceaux de taille ⌊n / k⌋ + 1 ou ⌊n / k⌋ et n'utilise pas le tri O (n log n).

get_chunk_id<-function(n, k){
    r <- n %% k
    s <- n %/% k
    i<-seq_len(n)
    1 + ifelse (i <= r * (s+1), (i-1) %/% (s+1), r + ((i - r * (s+1)-1) %/% s))
}

split(1:10, get_chunk_id(10,3))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.