2 votes

dplyr : calculer le pourcentage de niveaux dans plusieurs colonnes dans un data.frame et le convertir en large

DONNÉES

df <- data.frame(id=c(rep("site1", 3), rep("site2", 8), rep("site3", 9), rep("site4", 15)),
                 major_rock = c("greywacke",    "mudstone", "gravel",   "greywacke",    "gravel",   "mudstone", "gravel", "mudstone", "mudstone",   
                                "conglomerate", "gravel", "mudstone",   "greywacke","conglomerate", "gravel",   "gravel",   "greywacke","gravel",   
                                "greywacke",    "gravel",   "mudstone", "greywacke",    "gravel", "gravel", "gravel",   "conglomerate", "greywacke",
                                "coquina",  "gravel",   "gravel",   "greywacke",    "gravel",   "mudstone","mudstone",  "gravel"),
                 minor_rock = c("sandstone mudstone basalt chert limestone",  "limestone",   "sand silt clay", "sandstone mudstone basalt chert limestone",
                                "sand silt clay", "sandstone conglomerate coquina tephra", NA, "limestone",  "mudstone sandstone coquina limestone",
                                "sandstone mudstone limestone",  "sand loess silt",  "sandstone conglomerate coquina tephra", "sandstone mudstone basalt chert limestone",
                                "sandstone mudstone limestone", "sand loess silt", "loess silt sand", "sandstone mudstone conglomerate chert limestone basalt",
                                "sand silt clay",  "sandstone mudstone conglomerate", "loess sand silt", "sandstone conglomerate coquina tephra", "sandstone mudstone basalt chert limestone",
                                "sand loess silt", "sand silt clay", "loess silt sand",  "sandstone mudstone limestone", "sandstone mudstone conglomerate chert limestone basalt",
                                "limestone", "loess sand silt",  NA, "sandstone mudstone conglomerate", "sandstone siltstone mudstone limestone silt lignite", "limestone",
                                "mudstone sandstone coquina limestone", "mudstone tephra loess"),
                 area_ha = c(1066.68,   7.59,   3.41,   4434.76,    393.16, 361.69, 306.75, 124.93, 95.84,  9.3,    8.45,   4565.89,    2600.44,    2198.52,    
                             2131.71,   2050.09,    1640.47,    657.09, 296.73, 178.12, 10403.53,   8389.2,  8304.08,   3853.36,    2476.36,    2451.25,    
                             1640.47,   1023.02,    532.94, 385.68, 296.73, 132.45, 124.93, 109.12, 4.87))

Ce que je veux ?

Je dois préparer df pour une autre analyse qui exige que chaque site ait une seule ligne. Ainsi, dans le data.frame final df_fin chaque site aura la proportion des niveaux en major_rock et minor_rock et les noms de colonnes (variables) seront les niveaux de major_rock et minor_rock .

Je peux le faire pour chaque variable ( major_rock y minor_rock ) et ensuite les combiner comme ci-dessous

Ce que j'ai fait ?

Pour major_rock

library(tidyverse)

df_major_rock <- df %>% 
  dplyr::select(-minor_rock) %>% 
  dplyr::group_by(id, major_rock) %>% 
  dplyr::summarise(total_area = sum(area_ha)) %>% 
  dplyr::group_by(id) %>% 
  dplyr::mutate(percent_major = total_area/sum(total_area) * 100) %>% 
  dplyr::select(-total_area) %>% 
  tidyr::spread(major_rock, percent_major)

> df_major_rock
Source: local data frame [4 x 6]
Groups: id [4]

      id conglomerate  coquina     gravel greywacke   mudstone
* <fctr>        <dbl>    <dbl>      <dbl>     <dbl>      <dbl>
1  site1           NA       NA  0.3164205  98.97929  0.7042907
2  site2    0.1621656       NA 12.3517842  77.32960 10.1564462
3  site3   13.4720995       NA 30.7432536  27.80577 27.9788787
4  site4    6.1085791 2.549393 39.0992422  25.73366 26.5091274

Idem pour minor_rock

df_minor_rock <- df %>% 
  dplyr::select(-major_rock) %>% 
  dplyr::group_by(id, minor_rock) %>% 
  dplyr::summarise(total_area = sum(area_ha)) %>% 
  dplyr::group_by(id) %>% 
  dplyr::mutate(percent_minor = total_area/sum(total_area) * 100)%>% 
  dplyr::select(-total_area) %>% 
  tidyr::spread(minor_rock, percent_minor)

> df_minor_rock
Source: local data frame [4 x 15]
Groups: id [4]

      id limestone `loess sand silt` `loess silt sand` `mudstone sandstone coquina limestone` `mudstone tephra loess` `sand loess silt`
* <fctr>     <dbl>             <dbl>             <dbl>                                  <dbl>                   <dbl>             <dbl>
1  site1 0.7042907                NA                NA                                     NA                      NA                NA
2  site2 2.1784240                NA                NA                              1.6711771                      NA          0.147344
3  site3        NA          1.091484         12.562550                                     NA                      NA         13.062701
4  site4 2.8607214          1.328100          6.171154                              0.2719299              0.01213617         20.693984
# ... with 8 more variables: `sand silt clay` <dbl>, `sandstone conglomerate coquina tephra` <dbl>, `sandstone mudstone basalt chert
#   limestone` <dbl>, `sandstone mudstone conglomerate` <dbl>, `sandstone mudstone conglomerate chert limestone basalt` <dbl>, `sandstone
#   mudstone limestone` <dbl>, `sandstone siltstone mudstone limestone silt lignite` <dbl>, `<NA>` <dbl>

Ensuite, j'ai joint les deux data.frames ensemble ( df_major_rock y df_minor_rock ) de sorte que le cadre de données final df_fin aura 4 observations seulement (une ligne pour chaque site) et les variables seront les niveaux de major_rock y minor_rock

df_fin <- df_major_rock %>% 
  dplyr::right_join(., df_minor_rock, by="id")

Question

df_fin est exactement ce que je veux. Cependant, dans cet exemple reproductible, je n'ai montré que 2 variables (major_rock et minor_rock) que j'ai dû créer deux data.frames différents pour obtenir les proportions des niveaux de chaque variable et ensuite les joindre pour obtenir le résultat final df_fin . Dans mes données réelles, j'ai beaucoup de variables autres que major_rock y minor_rock que je veux obtenir les proportions de leurs niveaux pour chaque site également. Je pense qu'il devrait y avoir une approche plus simple ou plus courte que la mienne. Toute suggestion sera très appréciée ?

2voto

Ian Wesley Points 2653

Vous pouvez raccourcir un peu ce processus en utilisant data.table::dcast qui répartira vos données en colonnes. Vous pouvez ensuite utiliser rowSums pour calculer le pourcentage en une seule étape. Bien qu'il puisse y avoir une meilleure façon de procéder, j'ai enveloppé cette approche pour chaque colonne dans une boucle :

df_fin  <- data.frame(id = unique(df$id))
myColumns <- setdiff(colnames(df)[-1], "area_ha")

for (name in myColumns){
  dcastFormula <- paste0("id ~ ", name)
  tempdf <- data.table::dcast(df, dcastFormula, sum)
  tempdf[,-1] <-  tempdf[,-1]/rowSums(tempdf[,-1],na.rm = TRUE)*100
  df_fin  <- left_join(df_fin , tempdf, by ="id")
}

Comme toujours, il existe probablement plusieurs autres façons de procéder, mais cet exemple est un peu plus simple que votre point de départ. Il pourrait également être modifié en fonction de vos autres colonnes et/ou de la manière dont vous souhaitez les agréger.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X