Announcement

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

  • Translating stacked bar into twoway bar for large number of categories/columns

    I am using Stata 14.1 on Windows 7.

    I am attempting to create stacked bar graphs of student performance on specific items within an assessment. I'd like to have the categories be the specific items (1-10 for some subtests; 1-60 for others; 1-100 for yet others) and have the y-values be (a) proportion of students attempting the item, and (b) proportion of children answering correctly. (Or # in lieu of proportion - still deciding on that. But that's the easy part, so not the focus of this question.)

    For the tasks with few items, I've been able to generate beautiful [except for the typo in the legend - curse that stuck 'L' key] graphs that look exactly the way I'd like them to using the -graph bar- approach... (See attachment one.)Click image for larger version

Name:	Stacked success 1.png
Views:	1
Size:	38.4 KB
ID:	1322730
    But then when the number of items gets huge, the labels mangle each other and the width of the bars shrinks down to tiny. (See attachment two.) Click image for larger version

Name:	Stacked failure 1.png
Views:	1
Size:	50.6 KB
ID:	1322732
    The code I'm using for this is below:

    Code:
    graph bar ans_correct_ct ans_incorrect_ct if grade==`g' & language==`lang', over(item) bar(1, color(usaid_red) lcolor(black)) bar(2, color(usaid_darkgray) lcolor(black)) ///
                        blabel(total, position(inside) format(%2.0f) color(white) /*size(5)*/) stack ///
                        title("# of learners attempting and correctly answering" "each item of the ``subtest'_proper' subtest") ///
                        note("Source: Learner Assessment Test in `district_proper', October 2015") ///
                        legend(label(1 "# learners answering correctly") label(2 "# learners attempting")) graphregion(color(white)) // name(PhonemicAwareness - `lang_`lang'_proper' - Grade `g' - District `district')
                        //name(``subtest'_proper' - `lang_`lang'_proper' - Grade `g' - District `district'
    I've read in various places around Statalist and Stack Overflow that Nick recommends moving to -twoway bar- for this kind of issue. I've attempted to do so, but am making an absolute hash of it. (See attachment three.) Click image for larger version

Name:	Stacked failure 2.png
Views:	1
Size:	26.6 KB
ID:	1322731
    And I really am not sure where I'm going wrong in my various attempts to tweak the code. The code I'm attempting to use for the -twoway- approach is below:

    Code:
    graph twoway ///
                        (bar ans_correct_ct item if grade==`g' & language==`lang', barw(5) bcolor(usaid_red) lcolor(black)) ///
                        rbar ans_incorrect_ct ans_correct_ct item if grade==`g' & language==`lang', barw(5) bcolor(usaid_darkgray) lcolor(black) ///
                        /*blabel(total, position(inside) format(%2.0f) color(white) /*size(5)*/) stack*/ ///
                        title("# of learners attempting and correctly answering" "each item of the ``subtest'_proper' subtest") ///
                        note("Source: Learner Assessment Test in `district_proper', October 2015") ///
                        legend(label(1 "# learners answering correctly") label(2 "# learners attempting")) graphregion(color(white))    ///
                        /*name(``subtest'_proper'-`lang_`lang'_proper'-Grade`g'-District-`district')*/
    Note also that I'm currently commenting out the -name()- option in both of these because I keep getting an error message ("Phonemic Awareness - invalid name"). I've tried replacing all spaces with underscores, etc. and am not resolving the issue. I don't think I'm running into a character limit issue, but I'm too fried to figure out what else it might be.

    My goal would be to replicate the look of screenshot 1 for subtests with as many items as in screenshot 2: clean spacing, enough room for the in-bar labels to be legible, and either enough spacing for the x-labels to be legible or the capacity to only label every nth category. When I do this exercise manually in Excel, I can get the look I want simply by extending the graph's width beyond the borders of my screen and then zooming out to grab my screencap. Obviously that's kludgy...but it also got me wondering whether I could somehow define the 'canvas' that Stata writes to as so wide that everything would sort of auto-fit nicely. Didn't have any luck with that, either.

  • #2
    Are you unable to use line graphs? It will make it easier for end users to compare and understand the values if they are referencing locations vs area in general. You could also consider restructuring the data a bit so you could include some type of testlet/content strand ID and use that to create separate panels (e.g., pass that value to the by option). For your last question there are ysize and xsize options that specify the size of the image and an aspect ratio option to allow you to manipulate the amount of space allocated for the plot region. If you wanted something finer grained - and if you do these types of graphs regularly - you would probably benefit from using brewscheme to generate a scheme file with these attributes set. Then you would just pass the scheme name to the scheme option and not have to worry too much about the aesthetics.

    Comment


    • #3
      Thanks for the reply, wbuchanan!

      I managed to stumble across the -aspectratio- option (after hours of fruitless hunting) shortly after posting the above, and then re-discovered the -barw()- option, which helped too. So I've got that element of the display improved, but I'm still not having any luck with the explicit labeling of the values for each segment of the column. (I saw a post where Nick Cox used a bar chart and then overlaid a scatterplot consisting of the value labels, which I thought was an especially cool approach, but I haven't fully figured out how to adapt that for my purposes.)

      I actually stumbled across brewscheme and your presentations at the Stata User Group meetings while in the slightly earlier stages of trying to figure this out - brilliant stuff, by the way, very cool work! - but decided that I wasn't sufficiently certain that brewscheme was meant to do what I was trying to do to make it worth wrapping my head around it. (As in, it looks like a great solution for various design issues - but if I was going to be using it off-label, which I feared, then I thought there might be a more direct/appropriate avenue out there for me first.) Maybe I'm wrong, and just need to dive deeper into the documentation for it.

      As for the graph structure, what I've got isn't time-series data. It's distinct (independent?) test items, mostly on timed assessment tasks. The vast majority of kids will only get to the first ten, and then there's a sharp drop-off after that. My audience for these graphs doesn't have tons of experience reading graphs or thinking about them in rigorous ways - we're talking about mostly LDC government personnel with a high-school education who don't generally look at data visualizations very frequently - so I'm trying to do this in [what I think are] fairly simple ways:

      1) One column/bar per item.
      2) Grey bar height indicates # of children attempting it.
      3) Red bar height (which is always shorter than the grey) indicates # of children getting it right.
      4) No bar indicates no children attempted.

      ...I realized that I'd failed to note in the OP that I'm also wanting to add annotations for the last item that 50% of the kids attempted (which I'll do using -xline()- and -added_text_options-, I believe). So, basically, a bright red line running vertically through the item in question, w. a textbox beside it reading "50% of learners reached item x". But I think that part looks to be pretty straightforward. (Once I figure out how to derive that particular item in an automated way. That's proving thornier than I'd hoped. ...but this is how I learn new functions, right? Trial and error...)

      Comment


      • #4
        Timothy Slade here's an example of what I was suggesting:

        Code:
        // Clear data from memory
        clear
        
        // Set the random number seed
        set seed 7779311
        
        // Create grade levels
        set obs 6
        
        // Generate a grade level indicator (grades 3-8 since it is the largest 
        // assessment population in the US)
        qui: g grade = _n + 2
        
        // Create number of students per grade
        qui: g tested = rpoisson(3000) + int(runiform(175, 350))
        
        // Create student level observations
        expandcl tested, cl(grade) gen(stdid)
        
        // Give each student a value of theta
        qui: g theta = rnormal()
        
        // Generate item variables
        forv i = 1/100 {
            
            // These are all missing so different length tests can be constructed for 
            // separate grade levels
            qui: g keyedresponse`i' = .
            
        } // End Loop
        
        // Local with test lengths
        loc length 50 67 45 80 75 100
        
        // Loop over lengths/grades
        forv i = 1/6 {
        
            // Loop over items
            forv j = 1/`: word `i' of `length'' {
            
                // Simulate responses with a Rasch model
                qui: replace keyedresponse`j' =  rbinomial(1, exp(theta - rnormal()) / ///   
                                                    (1 + exp(theta - rnormal())))   ///   
                                                    if grade == `i' + 2
                
                // For the first ten items show smaller amount of missingness
                if `j' <= 10 {
        
                    // Add some missingness to each item responses
                    qui: replace keyedresponse`j' = . if                              ///   
                                    rbinomial(1, runiform(0, 0.3275)) == 1 & grade == `i' + 2
        
                } // End IF Block for first ten items
                
                // For items beyond the tenth
                else {
                
                    // Add increasing missingness as the length increases
                    qui: replace keyedresponse`j' = . if                              ///   
                                    rbinomial(1, runiform(0, 0.3275 + 0.01 * `j')) == 1 & grade == `i' + 2
                
                } // End ELSE Block for later items
                            
            } // End Loop over items
        
        } // End Loop over lengths/grades
        
        // Now the data get reshaped (e.g., normalized)
        reshape long keyedresponse, i(stdid grade theta tested) j(item)
        
        // Loop over the test lengths 
        forv i = 1/6 {
        
            // Drop records beyond the length of the test given to the student
            qui: drop if item > `: word `i' of `length'' & grade == `i' + 2
            
        } // End Loop 
        
        // Collapse down to grade/item records
        collapse (count) attempted = keyedresponse (sum) correct = keyedresponse ///   
        (first) n = tested, by(grade item)
        
        // Create percent attempted
        qui: g pctattempted = attempted/n
        
        // Create percent correct
        qui: g pctcorrect = correct/attempted
        
        // Creates indicator for your threshold
        qui: g cutline = pctattempted >= 0.5
        
        // Gets the item number the last time the threshold is met
        bys grade (item): egen cutval = max(item) if cutline == 1
        
        // Can then use summarize to get the value into a local
        qui: su cutval if grade == 4, meanonly
        
        // Line graphs showing percent attempted/percent correct
        tw line pctattempted item if grade == 4, lc(blue) lw(medthick) sort ||         ///   
        line pctcorrect item if grade == 4, lc(orange) lw(medthick) sort             ///   
        ysca(range(0 1)) ylab(#10, nogrid angle(0)) yti("Percent of students")         ///   
        xti("Item Number") xsca(range(1(1)67)) xlab(#67, angle(90) labsize(vsmall))  ///   
        xsize(17) ysize(11) graphr(ic(white) fc(white) lc(white))                     ///   
        legend(pos(12) label(1 "% of Students Attempting Item")                       ///   
        label(2 "% Correct from Attempted") symy(3.5) symx(3.5)                       ///   
        region(lc(white) fc(white) ic(white))) xline(`r(mean)')                      ///   
        text(1 `=`r(mean)' + 5' "50% of learners" "reached this item", size(vsmall)) ///   
        ti("Summary of Item Responses" "4th Grade Students", c(black) span)
        
        // Export the graph
        gr export ~/Desktop/exampleGrade4Line.png, as(png) replace
        
        // Create positional marker for pct correct values
        qui: g pctcorrectposition = 6
        qui: g pctattemptedposition = 12
        
        // Can then use summarize to get the value into a local
        qui: su cutval if grade == 4, meanonly
        
        // Create the stacked bar type example
        tw bar pctattempted item if grade == 4, fc(orange) lc(black) lw(vvthin) ||      ///   
        bar pctcorrect item if grade == 4, fc(green)  lc(black) lw(vvthin) ||         ///   
        scatter pctattempted item if grade == 4, msym(none) mlab(pctattempted)          ///   
                mlabp(12) mlabang(90) mlabc(black) mlabs(tiny) mlabgap(*5)              ///   
                mlabvpos(pctattemptedposition)  ||                                     ///   
        scatter pctcorrect item if grade == 4, msym(none) mlab(pctcorrect) mlabp(6)  ///   
                mlabang(90) mlabc(black) mlabs(tiny) mlabvpos(pctcorrectposition)      ///   
                mlabgap(*12.5)                                                         ///   
        ysca(range(0 1)) ylab(#10, nogrid angle(0)) yti("Percent of students")         ///   
        xti("Item Number") xsca(range(1(1)67)) xlab(#67, angle(90) labsize(vsmall))  ///   
        xsize(17) ysize(11) graphr(ic(white) fc(white) lc(white))                     ///   
        legend(pos(12) label(1 "% of Students Attempting Item")                       ///   
        label(2 "% Correct from Attempted") symy(3.5) symx(3.5)                       ///   
        region(lc(white) fc(white) ic(white)))  xline(`r(mean)')                      ///   
        text(1 `=`r(mean)' + 5' "50% of learners" "reached this item", size(vsmall)) ///   
        ti("Summary of Item Responses" "4th Grade Students", c(black) span)
        
        // Export the graph
        gr export ~/Desktop/exampleGrade4Bar.png, as(png) replace
        
        // Then repeat block multiple times for each grade level.
        The difficulty you may have are cases (like in the example) where a smaller proportion of students answer earlier items (e.g., &amp;lt; 50% respond to item 36 in the graph). With the line graphs, you can also discuss goal setting related to more immediate goals (e.g., of the students responding to the items a need to focus on providing instruction that raises the orange line), intermediate goals (e.g., trying to flatten the blue line out a bit more), and longer term goals (e.g., both the blue and orange lines need to move closer to the top of the graph). The big difference is really shifting the focus from a size/volume/area comparison to the point locations themselves. In the last bar of your first example it is fairly clear, but one of the pain points I've found with graphs like this when they aren't normalized is trying to get others to understand that they are comparing overlapping areas (e.g., 17 of the 19). I'm not sure which assessment you're using, but you may also run into issues with the test construction and how the proportion of students responding to an item is a function of the test itself; my wife does dyslexia research and has had to administer a ton of different assessments and mentioned a few cases where items continue to be presented to the students until they hit a threshold of incorrect responses.

        Comment

        Working...
        X