Skip to content

Instantly share code, notes, and snippets.

@cjdinger
Last active April 15, 2020 00:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cjdinger/c0d942dcbd41140976110022e0a54e34 to your computer and use it in GitHub Desktop.
Save cjdinger/c0d942dcbd41140976110022e0a54e34 to your computer and use it in GitHub Desktop.
SnackBot API and time series analysis
/*
Described in the blog post:
The Internet of Snacks: SnackBot data and what it reveals about SAS life
https://blogs.sas.com/content/sasdummy/snackbot-api-timeseries/
by Chris Hemedinger
*/
/* Use these ODS statements in SAS for Windows or SAS Enterprise Guide */
ods _all_ close;
filename results "%sysfunc(getoption(WORK))/snackbot.htm";
ods html5 file=results style=Htmlencore gtitle options(svg_mode="inline");
/* Use this one instead for SAS University Edition or SAS Studio */
/*
ods html5 (id=web) style=Htmlencore gtitle options(svg_mode="inline");
*/
%let start = '20MAY2018:0:0:0'dt;
/* format the start/end per the API needs */
%let start_time= %sysfunc(putn(&start.,is8601dt26.));
%let end_time= %sysfunc(datetime(),is8601dt26.);
/* Call the SnackBot API from snackbot.net */
filename resp temp;
proc http
method="GET"
url="http://snackbot.net/snackdata?start_time=&start_time.%str(&)end_time=&end_time.%str(&)utc_offset_minutes=-240"
out=resp;
run;
/* JSON libname engine to read the result */
/* Simple record layout, all in the ROOT member */
libname mms json fileref=resp;
/* raw count data at sampled timestamps */
/* with a few timestamp transformations, */
/* just for exploration */
data mmlevels;
set mms.root;
drop ordinal_root timestamp;
/* Convert the TIMESTAMP field to native value -- it's a character */
datetime = input(timestamp, anydtdtm.);
date = datepart(datetime);
time = timepart(datetime);
dow = date;
qhour = round(datetime,'0:15:0'T);
format datetime datetime20.
qhour datetime20.
date date9.
time timeampm10.
dow downame.;
run;
/* Empty data set with 15 minute interval slots */
/* Regular intervals for the entire "study" period */
data timeslots;
last = datetime();
length qhour 8;
format qhour datetime20;
drop last i;
do i = &start. to last by '0:15:00't;
qhour = i;
output;
end;
run;
/* Merge the sample data with the timeslots */
data expand;
merge mmlevels(keep=pieces qhour) timeslots;
by qhour;
run;
/* for empty timeslots, carry the sample data */
/* forward, so we always have a count of pieces */
/* Variation on a LOCF technique */
data final;
set expand;
length day $ 3;
day=put(datepart(qhour),weekdate3.);
retain hold;
if not missing(pieces) then
hold=pieces;
else pieces=hold;
drop hold;
if not missing(pieces);
run;
/* Decriptive stats broken down by day of week */
title "SnackBot readings per day-of-week";
proc means data=mmlevels mean stddev max min;
var pieces;
class dow;
run;
/* Count of readings per hour of the day */
title "SnackBot readings per hour";
proc freq data=mmlevels ;
table time / out=perhour;
format time hour2.;
run;
ods graphics / imagefmt=svg height=400 width=800;
title "SnackBot readings per hour";
proc sgplot data=perhour des="Readings per hour of day";
step x=time y=count;
xaxis min='0:0:0't max='24:0:0't label="Time of day" grid;
yaxis label="Servings";
run;
title 'Plain M&M pieces on S1 tracked by SnackBot';
ods graphics / imagefmt=svg height=300 width=1600;
proc sgplot data=final des='M&M pieces tracked by SnackBot';
/* plot the data as a series */
series x=qhour y=pieces / lineattrs=(color=navy thickness=3px);
/* Yes, these are the "official" M&M colors */
/* Will be applied in data-order, so works best when data */
/* begins on a Sunday */
styleattrs datacolors=(red orange yellow green blue CX593B18 red);
/* block areas to indicate days-of-week */
block x=qhour block=day / transparency=0.65
valueattrs=(weight=bold size=10pt color=navy);
xaxis minor display=(nolabel);
yaxis display=(nolabel) grid max=1600 minor;
run;
ods html5 close;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment