3
votes

Groupes d'âge en tranches mensuelles

J'ai du mal à trouver une solution au problème suivant. J'ai un df avec id / dob's et un autre monthbucket df comme suit

microbenchmark::microbenchmark(
  MM=  monthbucket %>% group_by_all %>% expand(id=df$id) %>%  left_join(.,{df %>% mutate(birth_month =cut(dob, "month"))},by="id") %>%  mutate(age=time_length(difftime(startmonth, birth_month),"years")) %>% 
    mutate(age_cat=case_when(age<19 ~ "<19", age>64 ~ ">64",TRUE ~ "19-64")) %>%  group_by(month) %>% count(age_cat) %>%  gather(variable, count, n) %>%
    unite(variable, age_cat) %>% spread(variable, count)
  ,
  AkselA = {ages <- as.data.frame(t(unclass(outer(monthbucket$startmonth, df$dob, "-")/365.25)))
  ages <- do.call(data.frame, lapply(ages, cut, c(0, 19, 64, Inf), c("0-19", "19-64", "64+")))
  ages <- sapply(ages, table)
  colnames(ages) <- monthbucket$month
  },
  Cole1 ={t(table(apply(X = outer(month_bucket, birth_days, `-`) / 365.25, MARGIN = 2, FUN = cut, c(0,19,65, Inf)), rep(format(month_bucket,'%Y-%m'), length(birth_days))))
   },
  # cole2={ cast(CJ(month_bucket, birth_days)[, .N, by = .(month_bucket , cut(as.numeric(month_bucket - birth_days)/365.25, c(0,19,65,Inf)))], month_bucket ~ cut, value.var = 'N')
  # },
  # 
  Cole3={crossing(month_bucket, birth_days)%>%count(month_bucket, age_range = cut(as.numeric(month_bucket - birth_days) / 365.25, c(0,19,65,Inf)))%>%spread(age_range, n)
  },

  Cole4={all_combos <- expand.grid(month_bucket =  month_bucket, birth_days = birth_days) 
  all_combos$age <- as.numeric(all_combos$month_bucket - all_combos$birth_days) / 365.25
  all_combos$cut_r <- cut(all_combos$age, c(0,19,65,Inf))
  reshape(data = aggregate( all_combos$month_bucket, by = list(bucket = all_combos$month_bucket,age_group = all_combos$cut_r), FUN = length), timevar = 'age_group' , idvar = 'bucket', direction = 'wide'  )
},
times = 1L)

Unit: milliseconds
   expr        min         lq       mean     median         uq        max neval
     MM 4249.02810 4249.02810 4249.02810 4249.02810 4249.02810 4249.02810     1
 AkselA   17.12697   17.12697   17.12697   17.12697   17.12697   17.12697     1
  Cole1 3237.94534 3237.94534 3237.94534 3237.94534 3237.94534 3237.94534     1
  Cole3   23.63945   23.63945   23.63945   23.63945   23.63945   23.63945     1
  Cole4  877.92782  877.92782  877.92782  877.92782  877.92782  877.92782     1

Je veux obtenir une sortie qui me donne le nombre de membres dans les groupes d'âge ( 64) pour chacun de mes buckets mensuels. Le décompte change évidemment selon l'année lorsque les gens ont des anniversaires.

J'ai obtenu le calcul de l'âge avec quelque chose comme:

set.seed(33)

df <- data.frame(dob = sample(seq(as.Date('1940/01/01'), as.Date('2010/01/01'), by="day"), 10000),
                 id = seq(1:10000) )


monthbucket <- data.frame(month = format(seq(as.Date("2010-01-01"),as.Date("2011-01-01"),by="months"),'%Y-%m'),
                          startmonth = seq(as.Date("2010-01-01"),as.Date("2011-01-01"),by="months"),
                          endmonth = seq(as.Date("2010-02-01"),as.Date("2011-02-01"),by="months")-1)


birth_days <- df$dob
month_bucket <- monthbucket$startmonth

Je suppose que l'approche générale serait pour calculer l'âge de chaque moisbucket, attribuez-le à l'une des 3 tranches d'âge et comptez-le par mois. Des suggestions?

MODIFIER 1.

Merci pour toutes les différentes approches, je viens d'exécuter un bref benchmark sur les solutions pour déterminer quelle réponse accepter. D'une manière ou d'une autre, la solution de table de données n'a pas fonctionné sur mon ensemble de données de test, mais je vérifierai dès que j'aurai quelques minutes dans les prochains jours.

age.fct <- function(dob, bucketdate) {

  period <- as.period(interval(dob, bucketdate),unit = "year")
  period$year}

et le benchmark

set.seed(33)

df <- data.frame(dob = sample(seq(as.Date('1940/01/01'), as.Date('2010/01/01'), by="day"), 10),
                 id = seq(1:10) )


monthbucket <- data.frame(month = format(seq(as.Date("2010-01-01"),as.Date("2011-01-01"),by="months"),'%Y-%m'),
                          startmonth = seq(as.Date("2010-01-01"),as.Date("2011-01-01"),by="months"),
                          endmonth = seq(as.Date("2010-02-01"),as.Date("2011-02-01"),by="months")-1)

Basé sur la vitesse, l'approche d'AkselA semble être la plus rapide mais j'obtiens un résultat différent pour l'approche de MM par rapport à tous les autres (une fois que AkselA passe à 65 dans la partie coupée cut, c (0, 19, 64, Inf) .. . J'accepterai la réponse en fonction de la vitesse mais je regarderai les différences dans les résultats!


1 commentaires

Pour l'option data.table , c'est dcast (...) au lieu de cast (...) . De plus, j'obtiens des repères extrêmement différents des vôtres, les médianes étant MM 59, Ansel 3.3, Cole_outer 2.8, Cole_dt 6.5, Cole_dplyr 5.9, Cole_reshape 5.3 en millisecondes. C'est avec 100 fois en microbenchmark.


3 Réponses :


3
votes

Pas très sophistiqué mais j'ai rejoint les deux tables (d'abord développé monthbucket sur df $ id ) puis j'ai calculé l'âge (comme vous avez tout le mois, je viens de calculé difftime avec le premier jour du mois de naissance et startmonth ). Ensuite, pour chaque mois (compartiment), j'ai compté le nombre de groupes d'âge différents et à la fin j'ai converti le format long en large pour une meilleure illustration.

library(lubridate)
library(tidyverse)

monthbucket %>% 
  group_by_all %>% 
  expand(id=df$id) %>% 
  left_join(.,{df %>%
                mutate(birth_month =cut(dob, "month"))},
            by="id") %>% 
  mutate(age=time_length(difftime(startmonth, birth_month),"years")) %>% 
  mutate(age_cat=case_when(age<19 ~ "<19",
                           age>64 ~ ">64",
                           TRUE ~ "19-64")) %>% 
  group_by(month) %>% 
  count(age_cat) %>% 
  gather(variable, count, n) %>%
  unite(variable, age_cat) %>% 
  spread(variable, count)

#> # A tibble: 13 x 4
#> # Groups:   month [13]
#>    month   `<19` `>64` `19-64`
#>    <fct>   <int> <int>   <int>
#>  1 2010-01     3     2       5
#>  2 2010-02     3     2       5
#>  3 2010-03     3     2       5
#>  4 2010-04     3     2       5
#>  5 2010-05     3     2       5
#>  6 2010-06     3     2       5
#>  7 2010-07     3     2       5
#>  8 2010-08     3     2       5
#>  9 2010-09     3     2       5
#> 10 2010-10     3     2       5
#> 11 2010-11     3     2       5
#> 12 2010-12     3     2       5
#> 13 2011-01     3     2       5

Créé le 03/07/2019 par le package reprex (v0.3.0)


0 commentaires

2
votes

En supposant que je comprends votre demande.

ages <- as.data.frame(t(unclass(outer(monthbucket$startmonth, df$dob, "-")/365.25)))

ages <- do.call(data.frame, 
  lapply(ages, cut, c(0, 19, 64, Inf), c("0-19", "19-64", "64+")))

ages <- sapply(ages, table)
colnames(ages) <- monthbucket$month
ages
#       2010-01 2010-02 2010-03 2010-04 2010-05 2010-06 2010-07 2010-08 2010-09 2010-10 2010-11 2010-12 2011-01
# 0-19        2       2       2       2       2       2       2       2       2       2       2       2       2
# 19-64       7       7       7       7       7       7       7       7       7       7       7       7       7
# 64+         1       1       1       1       1       1       1       1       1       1       1       1       1
# 


0 commentaires

2
votes

Il y a quelques similitudes avec la réponse de @ AkselA car elle dépend de external () , cut () et table () . XXX

Je me sentais bizarre d'avoir une solution similaire alors voici data.table:

all_combos <- expand.grid(month_bucket =  month_bucket, birth_days = birth_days)
all_combos$age <- as.numeric(all_combos$month_bucket - all_combos$birth_days) / 365.25
all_combos$cut_r <- cut(all_combos$age, c(0,19,65,Inf))

reshape(
  data = aggregate(
    all_combos$month_bucket
    , by = list(bucket = all_combos$month_bucket
                ,age_group = all_combos$cut_r)
    , FUN = length)
  , timevar = 'age_group'
  , idvar = 'bucket'
  , direction = 'wide'
)

dplyr et tidyr:

library(dplyr)
library(tidyr)

crossing(month_bucket, birth_days)%>%
  count(month_bucket
        , age_range = cut(as.numeric(month_bucket - birth_days) / 365.25, c(0,19,65,Inf))
        )%>%
  spread(age_range, n)

Et une approche similaire de base dont je ne suis pas complètement satisfait.

XXX


0 commentaires