BookmarkSubscribeRSS Feed
tc
Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10

A "Layered-Cocktail" Look at State Vote TotalsA "Layered-Cocktail" Look at State Vote Totals


Yet another election map, this one a cartogram of state "glasses" showing the percentage of combined Trump and Biden votes that went to each candidate. Reference lines provided to help show when a candidate exceeds the "half-full" mark. Voting data as of 11-14 from the New York Times.

 

UPDATE: Modified 11-14 at 10:04 CT to fix Trump/Biden color assignment code. Output was unchanged though - managed to make two mistakes that canceled each other out. 😀

* Fun With SAS ODS Graphics - Is the Glass Half-Trump or Half-Biden? (Yet Another Election Map)
  Data courtesy of NY Times  - nytimes.com/interactive/2020/11/03/us/elections/results-president.html;

*==> Get NY Times state-level Presidential election data (JSON format);
 
filename nyt '/folders/myfolders/NYTjsonResultsAsOf20201114.txt';
libname j json fileref=nyt;
                                      * Calculate % of votes for Biden & Trump; 
proc sql;                             * Dnominator is total Biden + Trump votes (others excluded); 
create table StateVotes as              
select r.state_id, 
sum(case when c.name_display='Joseph R. Biden Jr.' then c.votes end) as BidenVotes,
sum(case when c.name_display='Donald J. Trump' then c.votes end) as TrumpVotes,
calculated BidenVotes/(calculated BidenVotes+calculated TrumpVotes) as PctBiden,
calculated TrumpVotes/(calculated BidenVotes+calculated TrumpVotes) as PctTrump
from j.races r 
left join j.races_candidates c on r.ordinal_races=c.ordinal_races
group by state_id order by r.state_id;

*==> Generate USA cartogram map x/y coordinates from inline layout of state codes;

data states(keep=statecode x y y2);
input states $char80.;
y+1;
y2=y;                                * y-axis location of state name;
x=mod(y-1,2)*.5;                     * Offset alternate rows of states by .5;
do c=1+mod(y-1,2)*2 to 80 by 4;
  statecode=substr(states,c,2);
  x+1; 
  if statecode^='' then output; 
end;
datalines;
AK                                          ME
                                      VT, NH
    WA, MT, ND, MN, WI,     MI,     NY, MA, RI
      ID, WY, SD, IA, IL, IN, OH, PA, NJ, CT
    OR, NV, CO, NE, MO, KY, WV, MD, DE
      CA, AZ, UT, KS, AR, TN, VA, NC,         DC       
            NM, OK, LA, MS, AL, SC
              TX              GA
HI                              FL    
;
proc sort data=states; by statecode;

*==> Merge voting data and map points,
     Generate points for polygons (rectangles) to make "glasses" showing each candidate's share,
     Generate points for ellipses for top (Trump) and bottom (Biden) of "glasses"; 

data statevotespolygons;
merge states statevotes(rename=(state_id=statecode));
by statecode;                        * Assign sort sequence for bar chart;
eyR=y-.45; output; eyR=.;            * Y-axis points for ellipses at top and bottom of "glasses"; 
eyD=y+.45; output; eyD=.;            * Glasses" occupy .96 of height (2*.45 for rectangles + 2*.03 for ellipses);         
candidate="Biden";                    
p+1;                                 * Generate polygon points for Biden;
px=x-.48; py=y+.45; output; px=x+.48; output;     * Top left/right;
py=y+.45-pctBiden*.9; output; px=x-.48; output;   * Bottom left/right;
candidate="Trump";                   * Generate polygon points for Trump;
p+1;
px=x-.48; py=y-.45; output; px=x+.48; output;     * Top left/right;
py=y-.45+pctTrump*.9; output; px=x-.48; output;   * Bottom left/right;

*==> Map and bar charts of vote shares - polygon + ellipse + text plots);

ods listing image_dpi=300 gpath='/folders/myfolders';
ods graphics on / reset antialias width=11in height=8.5in imagename="PresidentialElection";

proc template;
define statgraph ustemplate;
  begingraph / subpixel=on;          * Define Dem/Rep color attributes;
  discreteattrmap name="candidate"; 
    value "Trump" / fillattrs=(color=red); 
    value "Biden" / fillattrs=(color=blue); 
  enddiscreteattrmap;
  discreteattrvar attrvar=candidatecolor var=candidate attrmap="candidate";
  layout overlayequated / xaxisopts=(display=none thresholdmin=0 thresholdmax=0) yaxisopts=(display=none reverse=true);
    layout gridded / columns=1 valign=top;  * Titles (Insets);
      entry textattrs=(size=24pt weight=bold color=black) "ELECTION 2020";
      entry textattrs=(size=8pt) " ";
      entry textattrs=(size=16pt weight=bold color=black) "IS THE GLASS " 
        textattrs=(size=16pt weight=bold color=red) "HALF-TRUMP" 
        textattrs=(size=16pt weight=bold color=black) " OR "
        textattrs=(size=16pt weight=bold color=blue) "HALF-BIDEN" 
        textattrs=(size=16pt weight=bold color=black) "?";
    endlayout;                         * Polts to show candidates' vote shares;
    polygonplot x=px y=py id=p / display=(fill) group=candidatecolor includemissinggroup=false;  * Rectangles showing candidates' vote shares;
     ellipseparm semiminor=.03 semimajor=.48 xorigin=x yorigin=eyR slope=0 / 
       display=(fill outline) outlineattrs=(color=white pattern=solid) fillattrs=(color=red) group=eyr includemissinggroup=false;  * Top of "glass" (Trump);
     ellipseparm semiminor=.03 semimajor=.48 xorigin=x yorigin=eyD slope=0 /
       display=(fill) fillattrs=(color=blue) group=eyd includemissinggroup=false;  * Bottom of "glass" (Biden);
    referenceline y=y / lineattrs=(pattern=dot color=white);  * Show 50%-of-vote reference lines;
    textplot x=x y=y text=statecode / textattrs=(color=white weight=bold size=12pt);  * Two-digit state codes;
    entry "  NOTES: 1. PERCENTAGES BASED ONLY ON VOTES CAST FOR TRUMP AND BIDEN. 2. VOTING DATA FROM NYTIMES.COM (AS OF 11-14)  " / valign=bottom;
  endlayout;
  endgraph;
end;
run;

proc sgrender data=statevotespolygons template=ustemplate;  * Generate the chart!;
6 REPLIES 6
RichardDeVen
Barite | Level 11

So two wrongs makes no difference 🤔

GraphGuy
Meteorite | Level 14

After I clicked on the image, I still had to increase my window size to increase the map size big enough to see the 50/50 reference lines. Perhaps making them a solid line, rather than dashed, would make them show up better when the map is viewed at less than 'native' resolution?

tc
Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10

Yea, I'm getting spoiled by higher-resolution displays. 😀 I did try solid lines, which could be seen better, but I didn't like the way they bisected the state codes (also tried backlighting letters, but felt that varied letter heights too much for this particular chart). In retrospect, I guess I probably should have tried varying the thickness of the dotted line though. Oh well, there's always next time!

RichardDeVen
Barite | Level 11

Instead of a stacked bar graph inside a glass, could you do side-by-side bars inside beer bottles, and audio annotate with 100 bottles of beer on the wall.... for the bumpy bus ride we are on.

RichardDeVen
Barite | Level 11

Here is the beer bottle version with side-by-side bars.

PresidentialElection.png

 

/* Modification of 
 * Fun With SAS ODS Graphics - Is the Glass Half-Trump or Half-Biden? (Yet Another Election Map)
 * to show each opponents support in beer bottles
 * 
 * Data randomly generated
 */ 

*==> Generate USA cartogram map x/y coordinates from inline layout of state codes;

data state_cartogram_points(keep=statecode x y);
  length statecode $2;
  format x y 3.;
  input;
  line = _infile_;
  if line ne: '/*';
  y + 1;                                * y-axis location of state name;
  do _n_ = 1 to countw(line,', ');
    statecode = scan(line, _n_, ', ');
    x = index(line, statecode);
    output;
  end;
datalines;
/* -|----10---|----20---|----30---|----40---|----50---| */
AK                                          ME
                                      VT, NH
    WA, MT, ND, MN, WI,     MI,     NY, MA, RI
      ID, WY, SD, IA, IL, IN, OH, PA, NJ, CT
    OR, NV, CO, NE, MO, KY, WV, MD, DE
      CA, AZ, UT, KS, AR, TN, VA, NC,         DC       
            NM, OK, LA, MS, AL, SC
              TX              GA
HI                              FL    
;
proc sort; by statecode; run;

data state_votes;
  set state_cartogram_points;
  by statecode;
  if first.statecode;

  call streaminit (123);

  name = 'Jrrgl Bmurgrgly         ';
  pct = round(47.5 + rand('uniform', 0, 5), 0.1); 
  _tot = pct;
  output;

  name = 'Dorggles Jrgllulrm';
  do _n_ = 1 to 1000 until (_tot + pct <= 100);
    pct = round(47.5 + rand('uniform', 0, 5), 0.1);
  end;
  _tot + pct;
  output;

  name = 'Erggglglia Gluggglr';
  pct = 100 - _tot;
  output;

  keep statecode name pct;
  format pct 6.2;
run;

*==> Merge voting data and map points,
     Generate points for polygons (rectangles) to make "glasses" showing each candidate's share,
     Generate points for ellipses for top (Trump) and bottom (Biden) of "glasses"; 

%macro rect(xvar=px, yvar=py, x=, y=, w=0.90, h=pct, ymult=3.5);
  &xvar = &x;      &yvar = (&y + 1) * &ymult;           output;
                   &yvar = &yvar - &h * &ymult / 100;   output;
  &xvar = &x + &w;                                      output;
                   &yvar = (&y + 1) * &ymult;           output;
%mend;

%macro bottles(xvar=pxo, yvar=pyo, x=x, y=y, w=0.91, h=96, ymult=3.5);
  /* x1,y1 is lower left  x2,y2 is upper right */

  h  = 0.96 * &ymult;
  h1 = 0.60 * &ymult;
  h2 = 0.76 * &ymult;
 
  x1 = &x;         y1 = (&y + 1) * &ymult;
  x2 = x1 + 0.91; y2 = y1 - h;

  polygon_id+1;
  &xvar = x1; &yvar = y1;  output;
  &yvar = y1-h1;  output;
  &xvar = x1+.15; &yvar = y1-h2;  output;
  &yvar = y2; output;
  &xvar = x2-.15; output;
  &yvar = y1-h2; output;
  &xvar = x2; &yvar = y1-h1; output;
  &yvar = y1; output;

  x1 + 1;
  x2 + 1;
  x3 + 1;
  x4 + 1;

  polygon_id+1;
  &xvar = x1; &yvar = y1;  output;
  &yvar = y1-h1;  output;
  &xvar = x1+.15; &yvar = y1-h2;  output;
  &yvar = y2; output;
  &xvar = x2-.15; output;
  &yvar = y1-h2; output;
  &xvar = x2; &yvar = y1-h1; output;
  &yvar = y1; output;
%mend;

options mprint;

data map_polygons (rename=name=who);
  merge 
    state_cartogram_points 
    state_votes
  ;
  by statecode;

  if name in: ('Jrrgl', 'Dorggles');

  if name =: 'Dorggles' then dx_who = 0; else dx_who = 1;

  polygon_id+1;
  %rect (x=x + dx_who, y=y);
  call missing (px, py);

  if first.statecode then do;
    pxt =  x + 1;
    pyt = (y + 1) * 3.5 + .5;
    output;
    call missing(pxt, pyt);

    %bottles ();
    call missing (pxo, pyo);
  end;

  format px py pxt pyt pxo pyo 7.2 polygon_id dx_who 4.;
run;

options nomprint;

*==> Map and bar charts of vote shares - polygon + ellipse + text plots);

*ods listing image_dpi=300 gpath='/temp';
*ods graphics on / reset antialias width=11in height=8.5in imagename="PresidentialElection";

ods listing image_dpi=96 gpath='/temp';
ods graphics on / reset antialias width=2200px height=1700px imagename="PresidentialElection";

proc template;
  define statgraph ustemplate;
    begingraph / subpixel=on;          * Define Dem/Rep color attributes;

      discreteattrmap name="who_colors"; 
        value "Jrrgl Bmurgrgly"    / fillattrs=(color=red); 
        value "Dorggles Jrgllulrm" / fillattrs=(color=blue); 
      enddiscreteattrmap;

      discreteattrvar 
        attrvar=who_color 
        var=who
        attrmap="who_colors"
      ;

      layout overlayequated / 
        xaxisopts=(display=none thresholdmin=0 thresholdmax=0) 
        yaxisopts=(display=none reverse=true)
      ;

        layout gridded / columns=1 valign=top;  * Titles (Insets);
          entry textattrs=(size=24pt weight=bold color=black) "ELECTION 2020";
          entry textattrs=(size=8pt) " ";
          entry textattrs=(size=16pt weight=bold color=black) "WHO HAS MORE? " 
            textattrs=(size=16pt weight=bold color=red) "JRRGL" 
            textattrs=(size=16pt weight=bold color=black) " OR "
            textattrs=(size=16pt weight=bold color=blue) "DORGGLES" 
            textattrs=(size=16pt weight=bold color=black) "?";
        endlayout;                         

        polygonplot x=px y=py id=polygon_id / 
          display=(fill) 
          group=who_color 
          includemissinggroup=false;  * Rectangles showing candidates' vote shares;

        polygonplot x=pxo y=pyo id=polygon_id / 
          display=(outline)
          includemissinggroup=false;

        textplot x=pxt y=pyt text=statecode / textattrs=(color=gray66 weight=bold size=8pt);

        entry 
          "  NOTES: "
          "1. PERCENTAGES BASED ONLY ON VOTES CAST FOR Dorggles AND Jrrgl."
          "2. VOTING DATA FROM RAND('UNIFORM')  " 
          / valign=bottom;
        ;
        
      endlayout;
    endgraph;
  end;
run;

proc sgrender data=map_polygons template=ustemplate;  * Generate the chart!;
run;
tc
Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10

Very clever! I vote for the %bottles macro to ship with Base SAS! 😀

SAS Innovate 2025: Call for Content

Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!

Submit your idea!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 6 replies
  • 1594 views
  • 11 likes
  • 3 in conversation