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

nytpage.png

 

Here's a SAS ODS Graphics take on a neat New York Times tile grid U.S. map that graced nearly a full page of the Jan 12th paper and was much-liked on Twitter. No details, unfortunately, on what the talented NYT folks used to prep the data, generate the chart and add the text, so I took a crack at the whole soup-to-nuts process with SAS code.

 

Note: To view a larger image, right-click above image and select Open image in new tab. To view a full-resolution image, visit Twitter post, click on thumbnail, then right-click next image and select Open image in new tab.

 

* Creating A New York Times-Like Tile Grid U.S. Map With SAS ODS Graphics
  Attempt to knockoff full-page graphic in 1-12-2022 NYT - see twitter.com/AlbertoCairo/status/1481289889421398026;
 
data NytMap;                                        * Generate coordinates for NYT tile map of U.S.; 
do r=1 to 8;                                        * Eight rows;
  input; 
  do c=1 to 11;                                     * Eleven columns;
    p=r*100+c;                                      * Assign 4-digit # to 88 cells of panel chart; 
    st=input(substr(_infile_,(c-1)*3+1,2),$2.);     * Grab 2-character state abbreviations;  
    output;  
  end; 
end;
datalines;                                          * NYT tile grid map "template";                                      
                              ME
                           VT NH
WA ID MT ND MN IL WI MI NY RI MA
OR NV WY SD IA IN OH PA NJ CT
CA UT CO NE MO KY WV VA MD DE
   AZ NM KS AR TN NC SC DC
         OK LA MS AL GA
AK HI    TX             FL
;                                                 * Read NYT COVID-19 state-level daily data;
filename nytdata url 'https://raw.githubusercontent.com/nytimes/covid-19-data/master/rolling-averages/us-states.csv'; 
proc import out=NYTdata(where=(date<='10jan2022'd)) file=nytdata dbms=csv replace; * NYT chart we're trying to replicate was thru Jan 10;

proc sql;                                         * Merge COVID-19 data with map coordinates and derive several fields;
create table NytDataMap as
select d.st, d.state, d.date, d.cases_avg_per_100k, m.p, md.maxdate,
       case when date=md.maxdate then d.cases_avg_per_100k end as EndingCases,
       case when date=md.maxdate then put(d.cases_avg_per_100k, 4.) end as EndingCasesX,
       case when pcm.prevMonthsMaxCases<currMonthMaxCases then 'Record Cases in January' else 'Cases Rising' end as color
from NytMap m 
left join (select fipstate(input(scan(geoid,2),2.)) as st, state, date, cases_avg_per_100k from nytdata) d on d.st=m.st
left join (select state, max(date) as maxdate from nytdata group by 1) md on d.state=md.state
left join (select state, max(case when date < '01jan2022'd then cases_avg_per_100k end) as prevMonthsMaxCases,
                         max(case when date between '01jan2022'd and '31jan2022'd then cases_avg_per_100k end) as currMonthMaxCases
           from nytdata group by 1) pcm on pcm.state=d.state
order by m.p, d.st, d.date;

data years;                                       * Show years the data covers (but only on WA cells);
st='WA'; p=3*100+1; yrY=.; 
do yrX='01jan2020'd, '01jan2021'd, '01jan2022'd; 
  yr="'"||substr(put(yrX,year4.),3); 
  output; 
end;

data NytDataMapYears; set nytdatamap years;       * Merge the NYT data and the year annotations;    
proc sort data=nytdatamapyears; by p st date;

data attrmap;                                     * Create an attribute to assign colors based on cases in January; 
ID="id"; value="Record Cases in January"; markercolor="red "; linecolor="red "; output;
ID="id"; value="Cases Rising           "; markercolor="gray"; linecolor="gray"; output;

*==> Use PROC SGPANEL with series and scatter plots to create the U.S. tile map;

ods listing image_dpi=200 gpath='/home/ted.conway/';
ods graphics / border=off reset antialias height=8.5in width=11in imagename="nytmap" antialiasmax=35000;
proc sgpanel data=nytdatamapyears(where=(p^=.)) noautolegend subpixel dattrmap=attrmap;
panelby p / start=topleft sort=(ascending descending) noborder novarname nowall columns=11 rows=9 missing noheader onepanel;
inset st / nolabel position=topleft textattrs=(color=black size=9.5pt weight=bold); * Show 2-digit state codes in insets;
series x=date y=cases_avg_per_100k / group=color attrid=id smoothconnect lineattrs=(pattern=solid thickness=1.5pt); * Show cases by day trend lines;
scatter x=date y=EndingCases / labelstrip datalabel datalabelattrs=(color=black weight=bold) datalabelpos=top markerattrs=(symbol=circlefilled size=11pt color=white) markeroutlineattrs=(color=white); * Create marker separated from line for ending date;  
scatter x=date y=EndingCases / group=color attrid=id markerattrs=(symbol=circlefilled) markeroutlineattrs=(color=white);
refline yrx / axis=x label=yr labelpos=min labelattrs=(color=black size=7pt weight=bold) transparency=.8 lineattrs=(color=black pattern=shortdash thickness=1.5pt); * Reference lines showing years (WA only);
colaxis display=none offsetmin=0 offsetmax=0;
rowaxis display=none offsetmin=0;
format EndingCases 4.;

*==> Use GTL to overlay text atop tile map image (one might just do this instead with editing software!);
                                                   
data dummy;                                        * Need "dummy" data for plot (one point); 
retain x y .5;

proc template;                                     * Specify text to overlay on map background image;
define statgraph image;                            
begingraph / subpixel=on border=false opaque=false;
layout overlay / xaxisopts=(display=none) yaxisopts=(display=none) walldisplay=none pad=0 outerpad=(top=0pt bottom=0pt left=0pt right=0pt);
scatterplot x=x y=y / markerattrs=(size=0);        * Dummy scatter plot (invisible marker);
                                                   * Set background to U.S. tile map created in prior step;
drawimage "/home/ted.conway/nytmap.png" / border=false height=100 layer=back heightunit=percent drawspace=graphpercent;
                                                   * Put headings + narrative + legend in space at top left of map image;
Layout Gridded / rows=10 order=columnmajor border=false autoalign=(topleft) pad=0 outerpad=0;
entry halign=left textattrs=(size=19pt weight=bold) "What to Make of Those Soaring Covid Counts " / pad=0 outerpad=0 ;
entry halign=left textattrs=(size=9pt weight=bold style=italic) halign=left "Note:" textattrs=(size=9pt style=italic weight=normal) " This is a knockoff of a 1-12-2022 New York Times graphic  " / pad=0 outerpad=0 ;
entry halign=left textattrs=(size=8.5pt) "Coronavirus case counts have reached record highs in the United States and continue to climb. Hospitalizations have surpassed last winter's  " / pad=0 outerpad=0 ; 
entry halign=left textattrs=(size=8.5pt) "wave. Deaths are also beginning to rise. The overall pattern is familiar, but a fresh perspective on how to interpret these metrics may be  " / pad=0 outerpad=0; 
entry halign=left textattrs=(size=8.5pt) "necessary as a faster but less severe variant tears through the country. Here's how to interpret the data in the coming days and weeks.  " / pad=0 outerpad=0 ; 
entry halign=left " " / pad=0 outerpad=0;
entry halign=left textattrs=(size=13pt weight=bold) "Average daily cases per 100,000 people  " / pad=0 outerpad=0 ; 
entry halign=left textattrs=(size=16pt color=red) {unicode '25cf'x} textattrs=(size=11pt color=black) " Record cases in January   " textattrs=(size=16pt color=gray) {unicode '25cf'x} textattrs=(size=11pt color=black) " Cases rising  " / pad=0 outerpad=0 ; 
Endlayout;
                                                     * Put notes in space at bottom right of map image;
Layout Gridded / rows=5 order=columnmajor border=false autoalign=(bottomright) pad=0 outerpad=0;
entry halign=left textattrs=(size=7.5pt) "  Note: All figures are 7-day " / pad=0 outerpad=0 ;
entry halign=left textattrs=(size=7.5pt) "  rolling averages as of Jan 10. " / pad=0 outerpad=0 ;
entry halign=left textattrs=(size=7.5pt) "  Source: New York Times  " / pad=0 outerpad=0 ;
entry halign=left textattrs=(size=7.5pt) "  database of reports from state " / pad=0 outerpad=0 ;
entry halign=left textattrs=(size=7.5pt) "  and local health agencies. " / pad=0 outerpad=0 ;
Endlayout;
endlayout;
endgraph;
end;
run; 
                                                      * Create the full map + text image!; 
ods listing image_dpi=200 gpath='/home/ted.conway/';
ods graphics on / reset antialias width=11in height=8.5in imagename="nytpage" border=off;
proc sgrender data=dummy template=image;
run;

 

Knockoff v. OriginalKnockoff v. Original

 

1 REPLY 1
GraphGuy
Meteorite | Level 14

Although I don't really like this kind of map, good job with the imitation (and subtle improvements) 🙂

 

sas-innovate-wordmark-2025-midnight.png

Register Today!

Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.


Register now!

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 1 reply
  • 1572 views
  • 6 likes
  • 2 in conversation