Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Stacked bar graph for >1 categorical vars

    Hi, I am trying to do something seemingly basic but surprisingly complicated - to plot the levels of multiple categorical variables as stacked bars in a single graph. The purpose is to depict the proportion of patients that had vs. did not have a given blood test done before, during, after their hospitalization.

    Code:
    * Generate flag for done / not done
    foreach var of varlist lab_cholesterol_before lab_cholesterol_during lab_cholesterol_after{
         gen `var'_done = 1 if !missing(`var')
         replace `var'_done = 0 if missing(`var')
    }
    I would like to plot a stacked bar graph with the x-axis containing 3 stacked bars side-by-side (one for each variable, representing the different time points) and the y-axis containing the count or percent of each response stacked (0 or 1). I've tried tabplot, catplot, graph bar, twoway bar, ... but can't seem to figure it out.

    Thank you for your help,
    Jonathan

  • #2
    Stacking bars of several categorical variables is indeed often wanted but usually it's harder than you want unless you reshape.

    An answer I've just posted seems possibly pertinent to that question.

    But IIUC you're plotting the means of three binary variables. Stacking serves no obvious purpose. Percent yes + percent no = 100. That's a case for graph dot or graph hbar unless there is detail you haven't given or I haven't grasped. (Different time points seem to be given by different variables, before, during, and after.)

    This gives the essence and most of the code is faking a data example that you don't have to do.


    Code:
    clear
    set obs 100 
    set seed 314159
    
    local p = 20
    foreach t in before during after {
        gen lab_cholesterol_`t' = cond(runiform() < `p'/100, 1, .)
        gen done_`t' = !missing(lab_cholesterol_`t')
        label var done_`t' "`t'"
        local p = `p' + 20 
    }
    
    graph dot done*, ascat

    Comment


    • #3
      Thanks Nick, the code you suggested creates missing indicator variables, and then plots the mean of these variables:

      Code:
      foreach var of varlist lab_cholesterol{
          gen done_`var' = 1 if !missing(`var')
          replace done_`var' = 0 if missing(`var')
      }
      graph bar done*, ascat
      While this depicts the % done for each variable, I was hoping, for visual effect, to have stacked bars that depict the % done and % notdone.

      I understand what you mean about reshape because if my dataset was in long format with a time variable then I could create separate done and notdone indicator variables:

      Code:
      foreach var of varlist lab_cholesterol{
          gen done_yes_`var' = 1 if !missing(`var')
          replace done_no_`var' = 1 if missing(`var')
      }
      graph bar (count) done_yes* done_no*, over(timevar) stack
      Since my data is in wide format with distinct variables for each time point, any other ideas I could try?

      Thanks as always,
      Jonathan
      Last edited by Jonathan Afilalo; 05 Sep 2024, 10:22.

      Comment


      • #4
        Not so about my code:

        It creates variables that are 1 if not missing and 0 if missing. That is just what your code does in two lines.

        It creates a graph from data in wide layout. I don't reshape and there is no need to do that.

        You're confirming that you want to stack two bars that add to 100%. You can do that.

        Here is something similar but not identical, a thermometer effect in which % no is tacit and % yes is explicit.

        Code:
        clear
        set obs 100 
        set seed 314159
        
        gen mean = . 
        local i = 0 
        
        local p = 20
        foreach t in before during after {
            local ++i 
            gen lab_cholesterol_`t' = cond(runiform() < `p'/100, 1, .)
            gen done_`t' = !missing(lab_cholesterol_`t')
            su done_`t', meanonly 
            replace mean = 100 * r(mean) in `i'
            local p = `p' + 20 
        }
        
        gen which = _n in 1/3 
        label def which 1 before 2 during 3 after 
        label val which which 
        gen one = 100
        
        twoway bar one which, fcolor(none) lcolor(black) barw(0.6)  ///
        || bar mean which, xla(1 2 3, tlc(none) valuelabel) base(0) pstyle(p1) barw(0.6) ///
        xtitle(Time) legend(off) ytitle(% applied) yla(0(25)100) ///
        || scatter mean which, mlabpos(12) pstyle(p1) ms(none) mlab(mean) mlabsize(medium) mlabformat(%3.1f)
        Click image for larger version

Name:	thermometer.png
Views:	1
Size:	26.1 KB
ID:	1763148

        Comment


        • #5
          Thanks again, but this does not work as I don't have a natural x-over variable (which in your example). Can I generate a plot like your thermometer plot for 3 independent variables?

          Also, is catplot deprecated? I can't install it with ssc anymore...

          Comment


          • #6
            Frankly. I don't gather that you're even looking at my code. I create three variables and then plot their means. That's as close to your situation as I can get, as you don't provide a data example.

            Otherwise put, which is created by my code. It doesn't need to exist beforehand. See the generate and replace statements. It's generated as missing and then populated with 3 means of 3 variables.

            catplot has been on SSC for about 20 years, and is still there. To fix a problem with installation, you'd need to say more about what you tried and what error messages you got.

            help netio may help with settings. You may be talking to the internet through a proxy, in which case Stata will need to know about that.

            Comment


            • #7
              I am indeed struggling with your code, but not for lack of effort I assure you... Would you be kind enough to provide the code to generate this plot using my varnames (cholesterol_before cholesterol_during cholesterol_after) without any ancillary code to generate a mock dataset. I think this would make it easier for me to follow!

              Comment


              • #8
                Code:
                gen mean = . 
                local i = 0 
                
                foreach t in before during after {
                    local ++i 
                    
                    gen done_`t' = !missing(lab_cholesterol_`t')
                    su done_`t', meanonly 
                    replace mean = 100 * r(mean) in `i'
                }
                
                gen which = _n in 1/3 
                label def which 1 before 2 during 3 after 
                label val which which 
                gen one = 100
                
                twoway bar one which, fcolor(none) lcolor(black) barw(0.6)  ///
                || bar mean which, xla(1 2 3, tlc(none) valuelabel) base(0) pstyle(p1) barw(0.6) ///
                xtitle(Time) legend(off) ytitle(% applied) yla(0(25)100) ///
                || scatter mean which, mlabpos(12) pstyle(p1) ms(none) mlab(mean) mlabsize(medium) mlabformat(%3.1f)
                Last edited by Nick Cox; 05 Sep 2024, 15:40.

                Comment


                • #9
                  I am very grateful for your patience in sticking with me on this. The code you provided seems to be missing something because the first part (copied below) generates only 3 observations for mean, each having a value of 100

                  Code:
                  foreach t in before during after {
                     local ++i
                     gen done_`t' = !missing(lab_cholesterol_`t')
                     su done_`t', meanonly
                     replace mean = 100 * r(mean) in `i'
                  }
                  I understand and successfully execute the other parts of your code, so if you can help me figure out the first part then I will be all set!

                  Comment


                  • #10
                    I found the missing line of code, I needed to add a replace to fill in the done_`t' variable with 0's if not missing:

                    Code:
                    gen mean = .
                    local i = 0
                    foreach var in lab_LDLI_0_first lab_LDLI_1_first lab_LDLI_2_first{
                        local ++i
                        gen `var'_done = 1 if !missing(`var')
                        replace `var'_done = 0 if missing(`var')
                        su `var'_done, meanonly
                        replace mean = 100 * r(mean) in `i'
                    }
                    Woohoo! Thanks so much.

                    Comment


                    • #11
                      #10 Thanks for the thanks, but there was no missing (meaning omitted) code.

                      You don't need two lines to create the indicator. They do no harm, but one line has the same effect.

                      In your terms

                      Code:
                      gen `var'_done = !missing(`var')
                      is a shorter way to do it, because !missing() yields 1 or 0 depending on whether its argument is not missing or missing.

                      So, there was no missing code. See e.g. https://www.stata.com/support/faqs/d...rue-and-false/


                      #9 Only three means need to be stored, so it's sufficient if they are stored in the first three observations.

                      Comment


                      • #12
                        Of course, you are right Nick, you are truly the STATA Jedi

                        (Any appetite to create a package that simplifies this unnecessarily complex workflow for us mere mortals?)

                        Comment


                        • #13
                          Thanks for the interesting suggestion, but what would the package do precisely?

                          Code:
                          graph hbar (mean) done_* 
                          would show the means although I prefer what

                          Code:
                          statplot done
                          _*

                          produces (statplot is from SSC).

                          If there was a small chorus asking for explanation of code for the framing (thermometer flavour), that might be worth writing up.

                          Otherwise I think I would point you in the direction of upsetplot or vennbar as showing more than just these means.

                          https://www.statalist.org/forums/for...lable-from-ssc

                          https://www.statalist.org/forums/for...lable-from-ssc

                          https://journals.sagepub.com/doi/pdf...6867X241258010

                          Comment


                          • #14
                            Clearly I was sceptical at first about a new command, but the idea grew slowly. https://www.statalist.org/forums/for...dable-from-ssc Thanks much for the suggestion.

                            Comment

                            Working...
                            X