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?
4 Réponses :
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
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)] .
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.
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.
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
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
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
"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?