web-dev-qa-db-fra.com

Diviser un vecteur en morceaux dans R

Je dois diviser un vecteur en n morceaux de taille égale en R. Je ne pouvais trouver aucune fonction de base pour le faire. De plus, Google ne m'a pas conduit nulle part. Donc, voici ce que je suis venu avec, j'espère que cela aide 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.

A bientôt, Sebastian

178
Sebastian

Une ligne qui se scinde 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() et ceiling():

> 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
264
Harlan
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
57
mathheadinclouds
simplified version...
n = 3
split(x, sort(x%%n))
24
zhan2383

Cela divisera la chose différemment de ce que vous avez, mais c'est quand même une structure de liste agréable, 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 ce qui suit, selon la façon dont vous voulez le formater:

> 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écuter quelques timings en utilisant 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: Changer de as.factor () à as.character () dans ma fonction l'a fait deux fois plus vite.

18
Tony Breyal

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
17
Scott Worland

Quelques variantes supplémentaires à la pile ...

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

Notez que vous n'avez pas besoin d'utiliser la fonction factor ici, mais vous voulez quand même sort dont le premier vecteur serait 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 assigner des index de caractère, au contraire des nombres dans les ticks à 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 simples enregistrés dans un vecteur. Notez que l’utilisation de sort pour obtenir des valeurs consécutives en x permet d’alphabétiser 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
Richard Herron

Vous pouvez combiner le fractionnement/découpage, comme suggéré par mdsummer, avec un 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
SiggyF

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 le commander, jetez un sort autour

7
frankc

Voici une autre variante. 

REMARQUE: avec cet exemple, vous spécifiez CHUNK SIZE 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 bloc.

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,|
6
eAndy

J'avais besoin de la même fonction et j'avais lu les solutions précédentes, mais j'avais aussi besoin du bloc déséquilibré pour être à la fin, c'est-à-dire que si j'ai 10 éléments pour les scinder en vecteurs de 3 chacun, 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
5
Zak D

Fonction simple pour diviser un vecteur en utilisant simplement des index - inutile de trop compliquer les choses

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])
    }))
}
4
Philip Michaelsen

Si vous n'aimez pas split() et vous n'aimez pas matrix() (avec ses NA pendants), 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 retourne une liste, mais il ne perd pas de temps et d'espace avec les étiquettes, il peut donc être plus performant.

3
verbamour

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 data.tables que la limite supérieure permet:

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 première ligne du nom original data.table. Le dernier fichier data.table peut être court et rempli d'AN. Vous devez donc sous-définir ce dernier en fonction des données restantes. Ce type de fonction est utile car certains logiciels SIG limitent le nombre de broches d’adresses que vous pouvez importer, par exemple. Donc, découper des data.tables en plus petits morceaux peut ne pas être recommandé, mais cela peut ne pas être évitable.

2
rferrisx

Merci à @Sebastian pour cette function

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

En utilisant le rep_len de la base R:

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 index 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
2
FXQuantTrader

Si vous n'aimez pas split() et que cela ne vous dérange pas, les membres de l'équipe nationale complètent 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
verbamour

Wow, cette question a eu plus de succès que prévu. 

Merci pour toutes les idees. Je suis venu avec 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) pour que cela fonctionne. Utiliser seq_along agit comme le rang (x) dans ma solution précédente, mais est en réalité capable de produire le résultat correct avec des entrées en double. 

0
Sebastian

Une autre possibilité est la fonction splitIndices du paquet 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
Matifou

Désolé si cette réponse est si tardive, 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 la division.

> 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
0
Laura Paladini

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))
0
Valentas