New R script: Plot Nike+ runs

June 5th, 2009 by John Leave a reply »

I’ve been playing around with R and Nike+ and thought I’d put the two together, so I wrote a little R script that pulls your public data from the Nike+ website and plot out the graphs. It’s a little rough around the edges (see below for a list of enhancements/fixes), but it generates plots. I like SlowGeek, but found their smoothing function a bit too “smooth” — it was shaving 1/2 mph from my run speeds. I wasn’t happy with that 😉 Read more to get the source code, below.

Sample: Sample

For an example of what it might look like, you can see my plots.

Here’s the script:

# Version: August 9th, 2009
# Author: John D. Lewis
# License: GPL

library(XML)

basePath <- "c:/temp/"
myUserID <- "9999999999"
runID <- 0
xnew <- c()
xold<-0
yx <- c()
y <- c()
x <- c()

# Get the run data and store the graph in a PNG
saveRunGraph <- function(userID, runID) {
 cat("processing graph ", runID, sep="")

 xnum <- 0
 xnew <- c()
 xold <- 0
 runstart <- character()
 areasum <- 0

 x<-numeric()
 cals <- 0

 newURL <- paste("http://nikeplus.nike.com/nikeplus/v1/services/widget/get_public_run.jsp?id=", runID, "&userID=", userID, sep="")
 t<-xmlTreeParse(newURL)
 runstart <- xmlValue(xmlRoot(t)[["sportsData"]][["startTime"]])
 times<-xmlSApply(xmlRoot(t)[["sportsData"]][["extendedDataList"]][["extendedData"]], xmlValue)

 cals<-as.numeric(xmlValue(xmlRoot(t)[["sportsData"]][["runSummary"]][["calories"]]))
 cat("cals = ", cals, sep="")

 if (cals > 0) {
 y<-sapply(strsplit(times, split=","), function(xstring) {
 xnum<-as.numeric(xstring);
 xnew<-((xnum-xold)*(3600/5280)*100*3.2808399)
 xold<-xnum;
 return(xnew);
 } )

 yx[[1]]<- 0
 if (length(y) > 1) {
 for (i in 2:length(y)) {
 yx[[i]]<-(y[[i]]-y[[i-1]]);
 }
 }

 x[1] = 0;
 for(i in 2:length(yx)) {
 x[i]<-x[i-1]+1/6;
 }

 # Calculate area under the line
 for (i in 2:length(yx)) {
 areasum <- areasum + (5*(yx[i]+yx[i-1]))
 }
 cat("areasum = ", areasum, sep="")  

 pngfilename <- paste("nike_run_", userID, "_", runID, ".png", sep="")
 pngfilenamewithpath <- paste(basePath, pngfilename, sep="")
 png( filename=pngfilenamewithpath, bg="white", width = 800, height = 600 )
 plot(x, yx, main=paste(runstart, " [Run ", runID, "]", sep=""), sub=paste("area = ", (areasum), sep=""), xlab="Time", ylab="Speed (mph)", col="gray")
 # Changing the f value to 1/20 results in smoother graphs, but misses the outerbounds
 # Changing the f value to 1/50 results in jagged graphs, but sticks to the source points more
 lines(lowess(x,yx, f=1/25, iter=.45), col="red")
 grid()
 cat("Done saving", pngfilename, "\n")
 dev.off()
 return(pngfilename)
 } else {
 cat("No calories burned, so skipping file.")
 return("na")
 }
}

# 1. Get list of all runs from Nike+ website
runlistxml<-xmlTreeParse(paste("http://nikeplus.nike.com/nikeplus/v1/services/widget/get_public_run_list.jsp?userID=", myUserID, sep=""))
runs<-(xmlRoot(runlistxml)[["runList"]])
cat("Generating graphs for ", length(runs), " runs\n\n", sep="")

outputHTMLFilename <- paste(basePath, "runlist.html", sep="")
htmlfile <- file(outputHTMLFilename, "w")
cat("<UL>", file=htmlfile)

# 2. Process each run individually
for (i in length(runs):1) {
 xnew <- numeric()
 xnum <- numeric()
 xold <- numeric()
 y <- numeric(6000)
 yx <- numeric()
 t <- c()
 times <- c()

 run <- runs[[i]]
 runID <- xmlAttrs(run)["id"]
 #cat("Fetching graph for run ", runID, "\n")
 filename <- saveRunGraph(myUserID, runID)
 cat("<LI><IMG SRC=\"", filename, "\"></LI>\n", sep="", file=htmlfile)
}

cat("</UL>\n", file=htmlfile)
close(htmlfile)
shell.exec(outputHTMLFilename)

Update (6/6/09):

  • Added date/time to head of each chart
  • Reversed order of list, so most recent run is at the top
  • Posted updated script

Update (8/9/09):

  • Removed extra commented-out debugging lines
  • Added shell.exec to open HTML automatically after completion

Enhancements:

  • Print the real time, rather than a long number (anyone know how to process Nike+’s timestamp in R?)
  • Better handling of the userID and basePath settings (config file?)
  • More flexibility in managing smoothing parameters (like runnerplus.com‘s “Less”/”More Detail” slider bar, which is very slick)

5 comments

  1. John says:

    Got it! With a little help from Engineer Dad, I’ll calculate the area as (y1+y2)*(delta-x)*(0.5) = (5)(y1+y2) (since delta-x always equals 10). Assuming that the total number of calories is correct, I could then calculate the ratio of area to calories, finally comparing that ratio between various graphs to find which is the most “efficient.” Hmmmm. You might have something there, Mr. Toadstool. Thanks for the input.

  2. Toadstool says:

    Well, it would really be a relative indicator anyway because it doesn’t account for things like bulding potential energy by running uphill or using potential energy by running downhill. I just thought it might be an interesting way to compare from one day to the next. …like is it better to run as fast as you can for a short time or can you actually burn more energy by jogging steadily for a longer time, or maybe the run – walk scheme gets you to your personal maximum burn. And knowing your penchant for turning data into information, I thought it might be of interest to you. Then again maybe it’s just knowledge for the sake of knowledge?

  3. John says:

    @Toadstool

    Interesting idea. Is there a standard calories-burned-per-X formula? Or maybe just a second axis showing calories burned over time?

  4. Toadstool says:

    How about “integrating” the velocity-time data to get the area under the curve which should aproximate energy expended, or calories burned if you prefer?

Leave a Reply

You must be logged in to post a comment.