2
votes

comment diviser un dataframe par des lignes spécifiques dans r

J'ai un aspect de data comme ceci:

bind_rows(d1, d2, d3) %>% as_tibble()

# A tibble: 6 x 4
      A     B     C     D
  <dbl> <dbl> <dbl> <dbl>
1     1     2     3     4
2     1     2     3     4
3     1     2     3     4
4    10    20    30    40
5    10    20    30    40
6    NA   200   300   400

C'était une mauvaise liaison par lignes et je voulais diviser les data en 3 sous-données ( d1 , d2 et d3 ) comme ceci:

REMARQUE: Dans ma situation réelle, d1 , d2 et d3 ont des nrow() . J'ai mis nrow(d1) = 3 , nrow(d2) = 2 et nrow(d3) = 1 juste pour simplifier la question dans cet exemple.

d1 <- data.frame(A = rep(1,3),  B = rep(2,3),   C = rep(3,3),   D = rep(4,3))
d2 <- data.frame(A = rep(10,2), B = rep(20,2),  C = rep(30,2),  D = rep(40,2))
d3 <- data.frame(        B = 200, C = 300, D = 400)

> d1
  A B C D
1 1 2 3 4
2 1 2 3 4
3 1 2 3 4
> d2
   A  B  C  D
1 10 20 30 40
2 10 20 30 40
> d3
    B   C   D
1 200 300 400

Et puis je pourrais les lier correctement en utilisant bind_rows de dplyr

data <- structure(list(A = c("1", "1", "1", "A", "10", "10", "B", "200"), B = c("2", "2", "2", "B", "20", "20", "C", "300"), C = c("3","3", "3", "C", "30", "30", "D", "400"), D = c("4", "4", "4", "D", "40", "40", NA, NA)), row.names = c(NA, -8L), class = c("tbl_df","tbl", "data.frame"))
data

> data
# A tibble: 8 x 4
  A     B     C     D    
  <chr> <chr> <chr> <chr>
1 1     2     3     4    
2 1     2     3     4    
3 1     2     3     4    
4 A     B     C     D    
5 10    20    30    40   
6 10    20    30    40   
7 B     C     D     NA   
8 200   300   400   NA    

Le problème est que je suis troublé par la façon d'obtenir la d1 , d2 et d3 de data .

Toute aide sera grandement appréciée!


0 commentaires

3 Réponses :


3
votes

Voici une solution tidyverse .

process_df prend un process_df données et définit les noms de colonne et supprime la première ligne.

cars %>% add_row(speed = 1, dist = 2, .before = TRUE)

Ajoutez une ligne d'en-tête contenant uniquement les noms de colonne.

Utilisez rowwise() et c_across() pour obtenir les valeurs de toutes les colonnes par ligne. Utilisez cette option pour identifier les lignes qui sont des lignes d'en-tête.

group_map appliquera une fonction sur chaque groupe et bind_rows combinera les résultats.

cars %>% add_row(!!!new_row, .before = TRUE)
# (Works)

Explication de l'utilisation de !!! dans new_row

set_names(names(.)) crée un vecteur nommé qui représente la ligne que nous voulons ajouter. Cependant, add_row n'accepte pas un vecteur nommé - il veut que les valeurs soient spécifiées comme arguments.

Voici un exemple simplifié.

cars %>% add_row(new_row, .before = TRUE)
# (Error)

add_row n'accepte pas un vecteur nommé, donc cela ne fonctionne pas.

new_row <- c(speed = 1, dist = 2)

!!! décompressera le vecteur comme arguments de la fonction.

data %>%
  add_row(!!!set_names(names(.)), .before = 1) %>%
  rowwise() %>%
  mutate(
    group = all(is.na(c_across()) | c_across() %in% names(.))
  ) %>%
  ungroup() %>%
  mutate(group = cumsum(group)) %>%
  group_by(group) %>%
  group_map(process_df) %>%
  bind_rows()
#> # A tibble: 6 x 4
#>   A     B     C     D    
#>   <chr> <chr> <chr> <chr>
#> 1 1     2     3     4    
#> 2 1     2     3     4    
#> 3 1     2     3     4    
#> 4 10    20    30    40   
#> 5 10    20    30    40   
#> 6 NA    200   300   400 

!!! ci-dessus aboutit essentiellement à ceci:

process_df <- function(df, ...) {
  df %>%
    set_names(slice(., 1)) %>%
    select(which(!is.na(names(.)))) %>%
    slice(-1)
}


4 commentaires

Désolé, je n'ai pas clarifié la question. Votre réponse fonctionne lorsque d1 , d2 et d3 ont le même nrow , mais ma situation réelle est que d1 , d2 et d3 ont un nrow différent et j'ai mis à jour la question.


@zhiweili, j'ai mis à jour ma réponse pour gérer les nouveaux exemples de données.


@ Paul, merci. Cela m'aide vraiment, mais il y a une petite question que je ne comprends pas bien la ligne de code commençant par add_row(....) . Je sais que ce code de ligne ajoute une nouvelle ligne en haut des data et je peux remplacer ce code de ligne par rbind(names(data), data) qui pourrait faire la même chose. Mais je veux toujours savoir la théorie comment cela fonctionne dans votre code, en particulier le !!! (Je google et dit: !!! est généralement utilisé pour évaluer une liste d'expressions, mais je ne comprends pas )


Voir ma réponse mise à jour pour l'explication.



2
votes

Est-ce que ça marche:

data
# A tibble: 5 x 4
  A     B     C     D    
  <chr> <chr> <chr> <chr>
1 1     2     3     4    
2 A     B     C     D    
3 10    20    30    40   
4 B     C     D     NA   
5 200   300   400   NA   
data <- rbind(LETTERS[1:4],data)
data
# A tibble: 6 x 4
  A     B     C     D    
  <chr> <chr> <chr> <chr>
1 A     B     C     D    
2 1     2     3     4    
3 A     B     C     D    
4 10    20    30    40   
5 B     C     D     NA   
6 200   300   400   NA   
split(data, rep(1:ceiling(nrow(data)/2), each = 2))
$`1`
# A tibble: 2 x 4
  A     B     C     D    
  <chr> <chr> <chr> <chr>
1 A     B     C     D    
2 1     2     3     4    

$`2`
# A tibble: 2 x 4
  A     B     C     D    
  <chr> <chr> <chr> <chr>
1 A     B     C     D    
2 10    20    30    40   

$`3`
# A tibble: 2 x 4
  A     B     C     D    
  <chr> <chr> <chr> <chr>
1 B     C     D     NA   
2 200   300   400   NA   


1 commentaires

Désolé, je n'ai pas clarifié la question. Votre réponse fonctionne lorsque d1 , d2 et d3 ont le même nrow , mais ma situation réelle est que d1 , d2 et d3 ont un nrow différent et j'ai mis à jour la question.



1
votes

Solution de base R:

df <- structure(list(A = c("1", "1", "1", "A", "10", "10", "B", "200"), B = c("2", "2", "2", "B", "20", "20", "C", "300"), C = c("3","3", "3", "C", "30", "30", "D", "400"), D = c("4", "4", "4", "D", "40", "40", NA, NA)), row.names = c(NA, -8L), class = c("tbl_df","tbl", "data.frame"))

Y compris l'envoi de data.frames séparés vers l'environnement mondial:

df <- structure(list(A = c("1", "A", "10", "B", "200"), B = c("2", "B", "20", "C", "300"), C = c("3", "C", "30", "D", "400"), D = c("4","D", "40", NA, NA)), row.names = c(NA, -5L), class = c("tbl_df", "tbl", "data.frame"))

Solution Tidyverse:

df %>%
  rbind(names(.), .) %>%
  t() %>%
  data.frame() %>% 
  type.convert() %>%
  split.default(cumsum(!sapply(., is.integer))) %>%
  Map(function(x){
    y <- setNames(t(x[,-1, drop = FALSE]), x[,1])
    data.frame(y[,!is.na(colSums(y)), drop = FALSE])}, .) %>%
  set_names(str_c('d', names(.))) %>%
  list2env(., .GlobalEnv)

Notez la solution ajustée pour refléter la modification de la question:

rdf <- type.convert(data.frame(t(rbind(names(df), df))))

dflist <- Map(function(x) {
  y <-
    setNames(t(x[, -1, drop = FALSE]), x[, 1])
  y[, !is.na(colSums(y))]
}, split.default(rdf, cumsum(!sapply(rdf, is.integer))))

list2env(setNames(dflist, paste0('d', names(dflist))), .GlobalEnv)

Nouvelle solution incluant push to Global Env:

rdf <- type.convert(data.frame(t(rbind(names(df), df))))

Map(function(x){
  y <- setNames(t(x[,-1, drop = FALSE]), x[,1]); y[,!is.na(colSums(y))]
}, split.default(rdf, cumsum(!sapply(rdf, is.integer))))

Solution Tidyverse ajustée:

library(tidyverse)
df %>%
  rbind(names(df), .) %>%
  split(cumsum(seq_len(nrow(.)) %% 2)) %>%
  Map(function(x){setNames(x[2,], x[1,])[,complete.cases(t(x))]}, .) %>%
  set_names(str_c('d', names(.))) %>%
  list2env(., .GlobalEnv)

Les données:

list2env(setNames(Map(function(x){setNames(data.frame(t(x[,2, drop = FALSE])), x[,1])[,!is.na(x[,1])]},
  split.default(cbind(X0 = names(df), data.frame(t(df))), c(0, seq_len(nrow(df)) %/% 2))),
    paste0('d', seq_len(ceiling(nrow(df) / 2)))), .GlobalEnv)

Données mises à jour:

Map(function(x){setNames(data.frame(t(x[,2, drop = FALSE])), x[,1])[,!is.na(x[,1])]},
  split.default(cbind(X0 = names(df), data.frame(t(df))), c(0, seq_len(nrow(df)) %/% 2)))


2 commentaires

Désolé, je n'ai pas clarifié la question. Votre réponse fonctionne lorsque d1 , d2 et d3 ont le même nrow , mais ma situation réelle est que d1 , d2 et d3 ont un nrow différent et j'ai mis à jour la question.


Si ma réponse répond à votre question initiale, veuillez la voter.