9
votes

Addition des valeurs dans R en fonction de la valeur de la colonne avec dplyr

J'ai un ensemble de données contenant les informations suivantes:

total = 0;
average = 0;
for(i in 1:length(Data$Subject)){
   for(j in 1:ncols(Data)){
   if(Data$UniqueNumber[i] > 0){
    total[i] = sum(Data[i,1:j])
    average[i] = mean(Data[i,1:j])
   }
}

Si la valeur de UniqueNumber> 0, j'aimerais additionner les valeurs avec dplyr pour chaque sujet des lignes 1 à UniqueNumber et calculez la moyenne. Donc, pour le sujet 001, somme = 2 et moyenne = 0,67.

Subject    Value1    Value2    Value3      UniqueNumber
001        1         0         1           3
002        0         1         1           2
003        1         1         1           1

Modifier: Je cherche uniquement à additionner le nombre de colonnes répertoriées dans la colonne «Numéro unique». Donc, cela parcourt chaque ligne et s'arrête à la colonne répertoriée dans 'UniqueNumber'. Exemple: la ligne 2 avec le sujet 002 doit additionner les valeurs des colonnes «Valeur1» et «Valeur2», tandis que la ligne 3 avec le sujet 003 ne doit additionner que la valeur de la colonne «Valeur1».


2 commentaires

Vous pouvez essayer df%>% mutate (sum = ifelse (UniqueNumber> 0, rowSums (. [ 2: (length (.) - 1)]), NA), mean = ifelse (UniqueNumber> 0, rowMeans (. [ 2: (longueur (.) - 1)]), NA)) .


@tmfmnk Je ne pense pas que votre code itérera sur la longueur de UniqueNumber. Il semble que mes résultats totalisent toute la colonne et ne s'arrêtent pas à la valeur de la colonne UniqueValue.


6 Réponses :


1
votes

Une solution qui utilise purrr :: map_df (qui est du même auteur que dplyr).

tt <- "Subject    Value1    Value2    Value3      UniqueNumber
001        1         0         1           3
002        0         1         1           2
003        1         1         1           1"

dat <- read.table(text=tt, header=T)

Une autre option ( format de sortie plus proche d'une solution dplyr ):

map_df(l_dat, function(x) {
  n_cols <- x$UniqueNumber
  id <- x$Subject
  x <- as.numeric(x[2:(n_cols+1)])
  tibble(id=id, 
                mean_values=sum(x, na.rm=T)/(length(x)-1)) # change here
})
# # A tibble: 3 x 2
# id mean_values
# <int>       <dbl>
# 1     1          1.
# 2     2          1.
# 3     3        Inf  #beware of this case where you end up dividing by 0

Juste à titre d'exemple j'ai ajouté un sum () code > puis divisé par length(x)-1:

map_df(l_dat, function(x) {
  n_cols <- x$UniqueNumber
  id <- x$Subject
  x <- as.numeric(x[2:(n_cols+1)])
  tibble(id=id, mean_values=mean(x, na.rm=T))
})
# # A tibble: 3 x 2
# id mean_values
# <int>       <dbl>
# 1     1       0.667
# 2     2       0.5  
# 3     3       1   

Data:

library(dplyr)
library(purrr)
l_dat <- split(dat, dat$Subject) # first we need to split in a list

map_df(l_dat, function(x) {
  n_cols <- x$UniqueNumber # finds the number of columns
  x <- as.numeric(x[2:(n_cols+1)]) # subsets x and converts to numeric
  mean(x, na.rm=T) # mean to be returned
})
# output:
# # A tibble: 1 x 3
#     `1`   `2`   `3`
#   <dbl> <dbl> <dbl>
# 1 0.667   0.5     1

p >


5 commentaires

Recevez l'erreur suivante lorsque j'exécute votre code: Erreur dans 2: (n_cols + 1): argument NA / NaN


Je n'ai pas cette erreur, l'avez-vous essayé sur mes données d'exemple? Si votre colonne "UniqueNumber" est nommée différemment, vous devez modifier cette partie x $ UniqueNumber en conséquence.


Merci. J'ai mes données avaient une colonne manquante donc le code s'est écrasé. Je suis revenu et a corrigé le problème, cela fonctionne!


Pouvez-vous modifier le dénominateur de la fonction «moyenne» pour qu'elle divise par 1 de moins? Je dois inclure la première valeur (c'est-à-dire Value1), mais c'est un point de départ. Je voudrais donc diviser par un de moins dans chaque cas (tout en supprimant les NA).


@statsguyz oui vous pouvez, vous pouvez faire ce que vous voulez dans la fonction, changez simplement mean () avec ce que vous voulez, je vais mettre à jour avec un exemple.



9
votes

Ce n'est pas un fan / expert tidyverse, mais j'essaierais cela en utilisant un format long. Ensuite, filtrez simplement par index de ligne par groupe, puis exécutez toutes les fonctions que vous souhaitez sur une seule colonne (beaucoup plus simple de cette façon).

library(data.table)

data.table(Data) %>% 
  melt(id = c("Subject", "UniqueNumber")) %>%
  .[as.numeric(gsub("Value", "", variable, fixed = TRUE)) <= UniqueNumber,
    .(Mean = round(mean(value), 3), Total = sum(value)),
    by = Subject]

#    Subject  Mean Total
# 1:       1 0.667     2
# 2:       2 0.500     1
# 3:       3 1.000     1

Une manière très similaire d'y parvenir pourrait être filtrage par les nombres entiers dans les noms de colonnes. L'étape de filtrage vient avant le group_by donc elle pourrait potentiellement augmenter les performances (ou pas?) Mais elle est moins robuste car je suppose que les cols d'intérêt sont appelés "Value #"

Data %>% 
  gather(variable, value, -Subject, -UniqueNumber) %>% #long format
  filter(as.numeric(gsub("Value", "", variable, fixed = TRUE)) <= UniqueNumber) %>% #filter
  group_by(Subject) %>% # group by Subject
  summarise(Mean = mean(value), Total = sum(value)) %>% # do the calculations
  ungroup()

## A tibble: 3 x 3
#  Subject  Mean Total
#     <int> <dbl> <int>
# 1       1 0.667     2
# 2       2 0.5       1
# 3       3 1         1

Juste pour le plaisir, ajouter une solution data.table

library(tidyr)
library(dplyr)

Data %>% 
  gather(variable, value, -Subject, -UniqueNumber) %>% # long format
  group_by(Subject) %>% # group by Subject in order to get row counts
  filter(row_number() <= UniqueNumber) %>% # filter by row index
  summarise(Mean = mean(value), Total = sum(value)) %>% # do the calculations
  ungroup() 

## A tibble: 3 x 3
#  Subject  Mean Total
#     <int> <dbl> <int>
# 1       1 0.667     2
# 2       2 0.5       1
# 3       3 1         1


5 commentaires

Edit: On dirait que peu de sujets n'avaient pas de UniqueValues. Besoin de vérifier cela. Tout fonctionne bien!


Existe-t-il un moyen de modifier cela pour traiter les valeurs manquantes? Aussi, est-il possible de calculer la moyenne avec un dénominateur prenant en compte les valeurs manquantes?


Qu'entendez-vous par valeurs manquantes? NA dans la colonne Value ? Ajoutez simplement na.rm = TRUE aux fonctions, par exemple récapituler (Moyenne = moyenne (valeur, na.rm = TRUE), Total = somme (valeur, na.rm = TRUE)) . Je ne suis pas sûr de comprendre votre deuxième question. Pouvez-vous s'il vous plaît montrer un exemple avec la sortie souhaitée?


Oh ok, c'est ce que je pensais. Et si je voulais modifier la colonne "Moyenne" pour modifier le dénominateur de la fonction moyenne par +1 ou -1, est-ce possible?


Je ne suis pas sûr de comprendre ce que vous voulez dire, mais vous pouvez faire summary (Total = sum (value, na.rm = TRUE), Mean = Total / n ())



2
votes

Vérifiez cette solution:

 Subject UniqueNumber   Sum  Mean Value1 Value2 Value3
  <chr>          <int> <dbl> <dbl>  <dbl>  <dbl>  <dbl>
1 001                3     2 0.667      1      0      1
2 002                2     1 0.5        0      1      1
3 003                1     1 1          1      1      1

Résultat:

df %>%
  gather(key, val, Value1:Value3) %>%
  group_by(Subject) %>%
  mutate(
    Sum = sum(val[c(1:(UniqueNumber[1]))]),
    Mean = mean(val[c(1:(UniqueNumber[1]))]),
  ) %>%
  spread(key, val)


1 commentaires

Comment cela donne-t-il exactement les bons résultats? Cela me donne des résultats erronés lorsque j'insère des NA aléatoires dans les données. Par exemple, essayez d'insérer NA dans Value1 dans la première ligne.



2
votes

OP pourrait être intéressé uniquement par la solution dplyr mais à des fins de comparaison et pour les futurs lecteurs une option de base R utilisant mapply

cols <- grep("^Value", names(df))

cbind(df, t(mapply(function(x, y) {
      if (y > 0) {
        vals = as.numeric(df[x, cols[1:y]])
        c(Sum = sum(vals, na.rm = TRUE), Mean = mean(vals, na.rm = TRUE))
       }
       else 
        c(0, 0)
},1:nrow(df), df$UniqueNumber)))

#  Subject Value1 Value2 Value3 UniqueNumber Sum  Mean
#1       1      1      0      1            3   2 0.667
#2       2      0      1      1            2   1 0.500
#3       3      1      1      1            1   1 1.000

Ici, nous sous-ensembles chaque ligne en fonction de son UniqueNumber respectif, puis calculons sa somme et mean si la valeur de UniqueNumber est supérieur à 0 ou bien ne renvoie que 0.


0 commentaires

3
votes

Voici une autre méthode qui utilise tidyr :: nest pour collecter les colonnes Values ​​ dans une liste afin que nous puissions parcourir la table avec map2 code>. Dans chaque ligne, nous sélectionnons les valeurs correctes dans la colonne de liste Valeurs et prenons respectivement la somme ou la moyenne.

library(tidyverse)
tbl <- read_table2(
"Subject    Value1    Value2    Value3      UniqueNumber
001        1         0         1           3
002        0         1         1           2
003        1         1         1           1"
)
tbl %>%
  filter(UniqueNumber > 0) %>%
  nest(starts_with("Value"), .key = "Values") %>%
  mutate(
    sum = map2_dbl(UniqueNumber, Values, ~ sum(.y[1:.x], na.rm = TRUE)),
    mean = map2_dbl(UniqueNumber, Values, ~ mean(as.numeric(.y[1:.x], na.rm = TRUE))),
  )
#> # A tibble: 3 x 5
#>   Subject UniqueNumber Values             sum  mean
#>   <chr>          <dbl> <list>           <dbl> <dbl>
#> 1 001                3 <tibble [1 × 3]>     2 0.667
#> 2 002                2 <tibble [1 × 3]>     1 0.5  
#> 3 003                1 <tibble [1 × 3]>     1 1

Créé le 2019-02- 14 par le package reprex (v0.2.1)


0 commentaires

1
votes

Je pense que le moyen le plus simple est de définir sur NA les zéros qui devraient vraiment être NA , puis d'utiliser rowSums et rowMeans sur le sous-ensemble approprié de colonnes.

Data <- structure(
  list(Subject = 1:3, 
       Value1 = c(1L, 0L, 1L), 
       Value2 = c(0L, 1L, NA), 
       Value3 = c(1L, NA, NA), 
       UniqueNumber = c(3L, 2L, 1L)), 
  .Names = c("Subject","Value1", "Value2", "Value3", "UniqueNumber"),
  row.names = c(NA, 3L), class = "data.frame")

ou transform (Data, sum = rowSums (Data [2: 4], na.rm = TRUE), mean = rowMeans (Data [2: 4], na.rm = TRUE)) pour rester dans la base R.

data

Data[2:4][(col(dat[2:4])>dat[[5]])] <- NA
Data
#   Subject Value1 Value2 Value3 UniqueNumber
# 1       1      1      0      1            3
# 2       2      0      1     NA            2
# 3       3      1     NA     NA            1

library(dplyr)
Data%>%
  mutate(sum  =  rowSums(.[2:4], na.rm = TRUE),
         mean = rowMeans(.[2:4], na.rm = TRUE))

#   Subject Value1 Value2 Value3 UniqueNumber sum      mean
# 1       1      1      0      1            3   2 0.6666667
# 2       2      0      1     NA            2   1 0.5000000
# 3       3      1     NA     NA            1   1 1.0000000


0 commentaires