8
votes

Décaler les valeurs dans les colonnes vers le haut s'il manque des valeurs ci-dessus

J'ai un bloc de données comme celui-ci:

  id var1 var2 var3
   A  100  100  200
   A  200  400  700
   A  300  500  800
   A <NA>  600 <NA>
   B  100  400  500
   B  200 <NA>  500
   B  300 <NA>  600

qui ressemble à ceci:

  id var1 var2 var3
   A  100  100  200
   A  200 <NA> <NA>
   A  300 <NA> <NA>
   A <NA>  400 <NA>
   A <NA>  500 <NA>
   A <NA>  600 <NA>
   A <NA> <NA>  700
   A <NA> <NA>  800
   B  100 <NA>  500
   B  200 <NA> <NA>
   B  300 <NA> <NA>
   B <NA>  400 <NA>
   B <NA> <NA>  500
   B <NA> <NA>  600

Je voudrais décaler les valeurs dans les colonnes up s'il manque des valeurs ci-dessus (par groupe). Le résultat devrait ressembler à ceci:

df <- data.frame(id = c("A", "A", "A", "A", "A", "A", "A", "A", 
                    "B", "B", "B", "B", "B", "B"),
             var1 = c("100", "200", "300", NA, NA, NA, NA, NA,
                      "100", "200", "300", NA, NA, NA), 
             var2 = c("100", NA, NA, "400", "500", "600", NA, NA,
                      NA, NA, NA, "400", NA, NA),
             var3 = c("200", NA, NA, NA, NA, NA, "700", "800",
                      "500", NA, NA, NA, "500", "600"))

Je ne sais pas comment faire cela. Des pensées?


3 commentaires

"NA" doit-il être NA (c'est-à-dire pas une chaîne?)


Modifiez votre question. NA est actuellement une chaîne.


Vos chiffres sont-ils vraiment de classe caractère ?


4 Réponses :


5
votes

Voici un concept approximatif utilisant data.table qui peut être affiné:

library(data.table)
# Helper function:
shift_up <- function(x) {
  n <- length(x)
  x <- x[!is.na(x)]
  length(x) <- n
  x
}

setDT(df)
df[, lapply(.SD, shift_up), id][!(is.na(var1) & is.na(var2) & is.na(var3))]

   id var1 var2 var3
1:  A  100  100  200
2:  A  200  400  700
3:  A  300  500  800
4:  A <NA>  600 <NA>
5:  B  100  400  500
6:  B  200 <NA>  500
7:  B  300 <NA>  600


3 commentaires

Salut snoram! Merci de m'avoir aidé avec ça! Vos codes fonctionnent bien! Je veux juste savoir s'il existe un moyen simple d'écrire "[! (Is.na (var1) & is.na (var2) & is.na (var3))]". Dans ma base de données actuelle, j'ai plus de 30 variables. Ce serait formidable si je n'avais pas à écrire celui-ci un par un. Merci encore!


Dans la dernière étape, spécifiez le .SDcols et vous pouvez utiliser ! Reduce ('&', .SD) pour créer le vecteur logique


De plus, s'il s'agit de toutes les colonnes à l'exception de id , vous pouvez faire comme: out <- df [ lapply (.SD, shift_up), id]; out [rowSums (is.na (out [! "id"])) <(length (out) - 1)] .



4
votes

Ne pensez pas que ce soit le moyen le plus efficace de le faire, mais une option

library(rowr)

df1 <- do.call(rbind, lapply(split(df, df$id), function(x) {
    data.frame(id = x$id[1], do.call(cbind.fill,c(sapply(x[-1], na.omit),fill = NA)))
}))
names(df1) <- names(df)
df1


#    id   var1   var2   var3
#A.1  A    100    100    200
#A.2  A    200    400    700
#A.3  A    300    500    800
#A.4  A   <NA>    600   <NA>
#B.1  B    100    400    500
#B.2  B    200   <NA>    500
#B.3  B    300   <NA>    600

Nous divisons le dataframe en liste de dataframe pour chaque id et pour chaque dataframe, nous supprimons les valeurs NA en utilisant na.omit et utilisons cbind.fill pour remplir les valeurs avec NA et enfin fusionner la liste des dataframes en une seule en utilisant rbind avec do.call.


2 commentaires

Salut Ronak! Merci pour l'excellent code! Vous vous demandez simplement s'il existe un moyen de garder les noms de colonnes inchangés? Dans ma base de données actuelle, les noms des colonnes sont des années; ce sera très déroutant si je le gâche. Merci encore pour votre aide!


@Ruilin oui, vous pouvez enregistrer la sortie dans un objet différent et la renommer en utilisant le dataframe d'origine. Mise à jour de la réponse avec ce changement.



3
votes

Voici une option avec data.table . Convertir le 'data.frame' en 'data.table' ( setDT (df) ), groupé par 'id', order l'autre colonne en fonction des valeurs NA, puis créez un index pour supprimer les lignes où tous les éléments sont NA

v1[order(is.na(v1))]
#[1]  1  2  3  5  7 NA NA

Ou en utilisant la même logique avec tidyverse . Regroupés par 'id', changez la commande ou les éléments dans toutes les autres colonnes avec mutate_all par order ing sur le vecteur logique ( est .na (colonne) ) et conserver les lignes ayant au moins un non-NA ( filter_at )

v1 <- c(1:3, NA, 5, NA, 7)
order(is.na(v1)) #gives the index of order
#[1] 1 2 3 5 7 4 6

Classement un vecteur / colonne basé sur une indexation logique est simple.

library(tidyverse)
df %>% 
   group_by(id) %>% 
   mutate_all(funs(.[order(is.na(.))])) %>% 
   filter_at(vars(var1:var3), any_vars(!is.na(.)))
# A tibble: 7 x 4
# Groups:   id [2]
#  id    var1  var2  var3 
#  <fct> <fct> <fct> <fct>
#1 A     100   100   200  
#2 A     200   400   700  
#3 A     300   500   800  
#4 A     <NA>  600   <NA> 
#5 B     100   400   500  
#6 B     200   <NA>  500  
#7 B     300   <NA>  600  

utiliser cet index pour changer l'ordre des valeurs

library(data.table)
df1 <- setDT(df)[,  lapply(.SD, function(x) x[order(is.na(x))]), id]
df1[df1[,!Reduce(`&`, lapply(.SD, is.na)), .SDcols = var1:var3]]
#   id var1 var2 var3
#1:  A  100  100  200
#2:  A  200  400  700
#3:  A  300  500  800
#4:  A <NA>  600 <NA>
#5:  B  100  400  500
#6:  B  200 <NA>  500
#7:  B  300 <NA>  600


2 commentaires

Merci pour la belle réponse. Pourriez-vous s'il vous plaît expliquer cette partie ` mutate_all (funs (. [Order (is.na (.))]))%>% dans cette réponse


@amrrs a mis à jour la réponse. Espérons que cela aide



0
votes

voici une solution de base, si votre cas réel ne comporte pas de facteurs, vous pouvez ignorer la première et la dernière ligne:

df[] <- lapply(df,as.character)
. <- lapply(split(df,df$id),lapply, na.omit)
. <- lapply(., function(x) lapply(x, `length<-`, max(lengths(x[-1]))))
df <- do.call(rbind,lapply(., do.call, what = data.frame))
df[] <- lapply(df, factor)

#     id var1 var2 var3
# A.1  A  100  100  200
# A.2  A  200  400  700
# A.3  A  300  500  800
# A.4  A <NA>  600 <NA>
# B.1  B  100  400  500
# B.2  B  200 <NA>  500
# B.3  B  300 <NA>  600


0 commentaires