Search code examples
rplotly

How to create an age pyramid using plotly in R?


I'm trying to plot an age pyramid in R using ploty, and I also want to add a dropdown menu list to filter the plot by year.

Here's the code I'm using:

    dadosbr <- data.frame(
  SEXO = sample(c("Masculino", "Feminino"), 7525, replace = TRUE),
  FAIXA_ETARIA = sample(c("0-19", "20-29", "30-39", "40-49", "50-59", "60 ou mais"), 7525, replace = TRUE),
  ANO_TRAT = sample(2019:2024, 7525, replace = TRUE))

sexo <- dadosbr %>%
  group_by(SEXO, FAIXA_ETARIA, ANO_TRAT) %>%
  tally() %>%
  ungroup()

sexo <- sexo %>%
  group_by(ANO_TRAT) %>%
  mutate(percent = round(n / sum(n) * 100,1))

sexo <- sexo %>%
  mutate(percent = ifelse(SEXO == "Feminino", -percent, percent))

plot_ly(sexo, hoverinfo = 'text', textposition = "none",
        text = ~paste('</br> Ano do Início do Tratamento: ', ANO_TRAT,
                      '</br> Sexo: ', SEXO,
                      '</br> Número de Casos de TBDR: ', n,
                      '</br> %: ', percent)) %>%
  add_trace(data = sexo[sexo$ANO_TRAT == 2019, ],
            x = ~FAIXA_ETARIA, split = ~SEXO, y = ~n, type = 'bar', name = '2019', visible = TRUE) %>%
  add_trace(data = sexo[sexo$ANO_TRAT == 2020, ],
            x = ~FAIXA_ETARIA, split = ~SEXO, y = ~n, type = 'bar', name = '2020', visible = FALSE) %>%
  add_trace(data = sexo[sexo$ANO_TRAT == 2021, ],
            x = ~FAIXA_ETARIA, split = ~SEXO, y = ~n, type = 'bar', name = '2021', visible = FALSE) %>%
  add_trace(data = sexo[sexo$ANO_TRAT == 2022, ],
            x = ~FAIXA_ETARIA, split = ~SEXO, y = ~n, type = 'bar', name = '2022', visible = FALSE) %>%
  add_trace(data = sexo[sexo$ANO_TRAT == 2023, ],
            x = ~FAIXA_ETARIA, split = ~SEXO, y = ~n, type = 'bar', name = '2023', visible = FALSE) %>%
  add_trace(data = sexo[sexo$ANO_TRAT == 2024, ],
            x = ~FAIXA_ETARIA, split = ~SEXO, y = ~n, type = 'bar', name = '2024', visible = FALSE) %>%
  layout(width = 820, 
         xaxis = list(title = "Raça/cor", linecolor = 'black', showline = TRUE, showgrid = FALSE),
         yaxis = list(title = 'Número de Casos de TBR', showgrid = FALSE, zeroline = TRUE,
                      linecolor = 'black', range = c(0, 100)),
         colorway = c("#4567a9", "#118dff", "#107dac", "#1ebbd7", "#064273", "#71c7ec"),
         barmode = 'stack',
         showlegend = FALSE,
         updatemenus = list(
           list(
             active = 0,
             buttons = list(
               list(method = "restyle",
                    args = list("visible", list(TRUE, FALSE, FALSE, FALSE, FALSE, FALSE)),
                    label = "2019"),
               list(method = "restyle",
                    args = list("visible", list(FALSE, TRUE, FALSE, FALSE, FALSE, FALSE)),
                    label = "2020"),
               list(method = "restyle",
                    args = list("visible", list(FALSE, FALSE, TRUE, FALSE, FALSE, FALSE)),
                    label = "2021"),
               list(method = "restyle",
                    args = list("visible", list(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE)),
                    label = "2022"),
               list(method = "restyle",
                    args = list("visible", list(FALSE, FALSE, FALSE, FALSE, TRUE, FALSE)),
                    label = "2023"),
               list(method = "restyle",
                    args = list("visible", list(FALSE, FALSE, FALSE, FALSE, FALSE, TRUE)),
                    label = "2024")
             )
           )
         ),
         margin = list(l = 0, r = 0, b = 0, t = 0, pad = 0)
  )

I don't know what's going wrong. I even tried to chage it to "stack", but it doesn't work. I want the output to be something like this:

enter image description here

I made this one with ggplot, but I don't know how to do it with plotly.


Solution

  • Here is a solution to what you've asked based on what I think you're looking for. (I used this Python answer to answer your question in R.)

    Using the data as you've already structured it:

    • I used set.seed(23) to create the data consistently when working through this answer (if you wanted to see the same results).

    • To align the male and female bars you need barmode = "relative".

    • To create horizontal bars, in the call for plot_ly, add orientation = "h".

      • If you set the bars to a horizontal orientation, you'll need to switch the content you've assigned to xaxis and yaxis in the call to layout()
    • To make the content you've assigned to text hover content (a tooltip), change the assignment from text = to hovertext = . (Since you've set textposition = "none", I'm assuming that was your objective... although, your image shows text content...so...)

      • If you don't want the x, y that shows at the top of each tooltip, as it is redundant, then assign this to hovertemplate instead of hovertext or text. The last image in this answer has this change. (You can compare the tooltips in the last 2 images.)
    • I'm not sure what the objective was with the color assignments. In your code you've set a color for what seems like each year, but your ideal (based on the image) has colors moderated by gender. Once you've used the buttons by year, colors by year is a bit redundant. Here is an example of how you could assign colors by gender, so that it matches the visual you've provided. If you wanted to stick to color by year let me know, and I'll add content on how to do it that way, as well.

    • I've added code to make the percentages that are negative for the sake of coding, positive values

      • On the y-axis by setting ticktext & tickvals.
      • In hovertext =, added abs() to show absolute values. Otherwise, this content matches your original code assigned to text = . (I annotated this change in my comments in the code, as well.)

    The code you've assigned to buttons = is identical to what I created with lst = & lapply()

    lst <- rep(F, length(unique(sexo$ANO_TRAT)))    # visibility by trace
    # create btns
    btns <- lapply(1:length(lst), \(w) {            # create buttons
      lst[w] <- T                                   # change one button's visibility
      list(method = "restyle", label = sort(unique(sexo$ANO_TRAT))[w],
           args = list("visible", lst))
    })
    
    # the plot
    plot_ly(data = sexo, type = "bar", orientation = "h", 
            name = ~SEXO, color = ~SEXO, split = ~ANO_TRAT, # split traces by year 
            x = ~percent, y = ~FAIXA_ETARIA,                # data to plot
                                                            # tooltip content 
            hovertext = ~paste('</br> Ano do Início do Tratamento: ', ANO_TRAT,
                               '</br> Sexo: ', SEXO,
                               '</br> Número de Casos de TBDR: ', n,
                               '</br> %: ', abs(percent))) %>%    # <---- I added abs() here!
      layout(xaxis = list(title = "Raça/cor", 
                          tickmode = "array",
                          tickvals = seq(-10, 10, length.out = 5), # positive axis vals
                          ticktext = abs(seq(-10, 10, length.out = 5))), 
                                                    # standoff = space btw title & labels
             yaxis = list(title = list(text = 'Número de Casos de TBR', standoff = 20)),
             barmode = "relative",                  # align the genders' bars horizontally
             updatemenus = list(list(
               pad = list(r = 55),                  # create space between graph & dropdown
               showactive = T,                      # highlight visible trace's button
               buttons = btns                       # from lapply
             ))) %>% 
      style(visible = F, traces = c(2:6, 8:12))     # set 2019 as only visible trace(s)
    
    

    enter image description here

    enter image description here

    With the tooltip code set to hovertemplate instead of hovertext or text, this is what it looks like: enter image description here

    If you have any questions or if there's anything else, let me know.