Using JMP to Win Your Fantasy Football League
How much predictability is there in fantasy football? Many football fanatics like myself wonder year after year if there is any path to consistent success in fantasy leagues. Let's use JMP to find out!
With enhancements to JMP's Python integration, getting your fantasy football league data into JMP is easier than ever. After briefly showing how to use JMP's native HTTP Request functionality to access documented APIs from fantasy football providers like ESPN and Sleeper, this talk focuses on showcasing how to use existing API wrappers from the Python community to pull in years of data from fantasy leagues. From league standings, playoff results, NFL player statistics, and fantasy outputs to fantasy draft steals and busts, there is so much data to explore!
Once the data is imported and organized into tables, we can then get to work using JMP's statistical discovery tools to both explore and analyze our league-specific data, finding possible trends and patterns to give us an edge.
Does success mainly come from nailing the draft or from making trades and acquisitions throughout the year? Are breakout fantasy performances repeatable from players, year over year? How important is having a top three fantasy producer on your team? Are running backs still the biggest impact positions in fantasy, or have things shifted in favor of wide receivers and quaterbacks? These are the questions we explore to discover if JMP can help you win your next league.
Hello, everyone. My name is Nick Holmes. I am a test engineer here at JMP. Thank you for joining me today. Today, we're going to talk about how you can use JMP to win your fantasy football league. You can see I said maybe because obviously, there are no guarantees in fantasy football. Today, we're going to work on getting our fantasy football data into JMP, running some analysis, and just seeing what we can find out.
A little overview about myself. I would consider myself a fantasy football I'm a fanatic, if you will. I'm a Commissioner of a 12-person redraft league that has existed since about 2015. That is mainly the data that we're using for our examples and analysis today. A motivation behind this presentation or this project was, I've won the league in 2022, and then in 2023, I've finished in last place. I'm just looking for some predictability or some analysis that can help me to predict my outcomes in fantasy football.
I'm not a statistician, but I'll be doing my absolute best. We will be using data from both ESPN and Sleeper. ESPN, you're probably all familiar with, it's just a fantasy football provider. Sleeper is the same thing. It's just a different platform to host your fantasy football league. We'll be importing data from both of those platforms and using that for our analysis.
I used the SPN from about 2015 to 2020 and Sleeper from 2021 to present, so that's why we needed to get data from both of those platforms. It's a little overview of what we're going to be doing today to get this data into JMP and then to analyze it. First, we need to use APIs in order to import the data. We're going to do this in a couple of ways, one using New HTTP Request and another using JMP-Python integration.
We're then going to organize, or I'm going to show you how you can organize these requests into JMP classes or JMP class objects. Once we have this data in JMP, we need to organize it into JMP data tables. There are a couple of methods to do that, I'll overview on JSON to data table, which is a function native to JMP, as well as working with associative arrays and using JSL to create JMP data tables.
Once we have the data in JMP data tables, we'll then do some exploration and analysis. The first part of that we'll be doing some data table manipulation. I will show that using Workflow Builder, and then we'll do some JMP visualizations and some analysis in some of JMP's platforms.
All right, so what is an API? You can see the definition for an API there on the side, but I'll just briefly say an API is a way that we can retrieve data from a web server. There are a few methods associated with HTTP, get, post, delete, and put, but today we'll be focusing on probably the most simple, which is GET. We'll be sending a get request to a URL or API endpoint, and then that server will give us back a JSON string, which we'll then use to create an associative array in JMP as well as create our JMP data tables.
New HTTP request is a function in JMP that allows us to use JSL to communicate with these REST APIs, and this function allows the use of, as I mentioned before, get, post, put, and delete. Today, we'll be focusing on get mainly, and we'll see just how simple it is to use new HTTP request to get our data into JMP.
I want to show you first getting data from the fantasy League Sleeper. If you do a quick Google search for their API, you'll actually be brought to some pretty good documentation that tells you exactly what HTTP request endpoint to use and the type of data that you can expect to get back.
This is actually best case scenario for working with an API, is the provider gives you a very clear API as well as the type of data you're going to get back and just gives you really clear documentation. As you can see from their documentation, it gives us an outline of how we can get our league data, how we can get our roster data into JMP. It looks like for most of these, the only information we need to apply to the URL is this league ID.
You can see in this example here, I have just an outline of what this might look like in JMP. We supply our own league ID and define that as a URL variable. Then we send that request using new HTTP request, providing the URL with the method get. We send the request, and then we parse the JSON that's sent back to JMP.
Now that we've taken a look at what this request might look like, let's see how that behaves in JMP. You see we have a JMP script window open here. If you take a look at this URL defined, you see we have our league ID and the request that you saw in the slide. If we run this here, you see that we get returned a JSON string. In order to interact with this data better in JMP, we'll want to change that over into a JMP associative array, which is the purpose of this Parsed JSON function here.
You see we get an associative array assigned. If we take a look at this associative array and then subscript it to get the name of our league, you can see we get return league-certified fantasy football, which is the name of the league. Now that I've shown you a very simple demo, now, I want to show you how we can interact or organize our queries into JMP classes in order to organize our request, our HTTP request. Let's close this out and open up our project file. Taking a look at this project file here, you'll see we have classes defined in order to organize our queries.
We have this league class. As you can see from this reference here, this is actually drawn from a Python API that I borrowed the outline for, and we'll go into that a little bit more when we talk about our JMP Python integration. Just taking a look at this class, you'll see that we have classes for league users, players, draft, and stats. All of these classes in the back end are calling this super class-base API, which is what's making our new HTTP request in the background.
Let's take a look at how we might use this class in order to get our data. I'll run this class definition and then scroll down here to the bottom where I have some examples. Excuse me if you get dizzy from the scrolling. Now you'll see I'm defining a league object. Again, providing it with my league ID. You can see here we have the class definition, and now we can use the methods defined for that particular object in order to get our rosters for the league. We can get our users for the league, and so on and so forth. All of this was returned in an associative array.
We won't worry about looking exactly what's in there for now, but just to show you the data that we get back. Now we have the same thing for users, players, and drapes. I'll be sharing all of this with the JMP community as presentation materials along with the slides. You should be able to use these classes in the one I show later in order to import your data into JMP.
We saw for Sleeper, getting our data was very easy. There was a very well-defined API that we were able to use in order to create our HTTP request. Unfortunately, it doesn't seem like that's the case with ESPN. If you do a quick search for ESPN API or ESPN fantasy API, you'll see that the only thing you get back is this Python class that someone's created. There doesn't seem to be publicly available documentation for their API.
What do we do? Well, now it's a perfect time to talk to you about JMP, New Python integration in JMP 18. Now at JMP 18, Python is installed with JMP. We can utilize Python packages with extremely minimal setup. With those classes that I showed you before from Sleeper that I showed you or inspired by Python classes, instead of building the classes and the method definitions ourselves, we can just install the Python package using that line of script you see here and interact with Python in order to bring the data into JMP.
Since this exists for ESPN, we can now do the same thing for ESPN, and since there's no good documentation for them, we can instead rely on the pre-existing Python API that someone has already written. What are some of the benefits of using Python integration? All of the hard work is done for us. There's no need for us to code the logic of API calls or any other type of data manipulation for functions that we want to make. We can just rely on the functions and documentation that other people have written in Python.
We simply call the Python functions and send that data back to JMP. Here's a simple example of what that might look like. Apologies if it's a little bit harder to see, but I wanted to fit it all on the screen. Again, these slides will be included for you to look at later, but you can just see by looking at the size of the function definition. Finding our own get scoreboard function is a lot more complicated than using Python integration in order to call the get_scoreboard function that someone's already defined for us in Python.
Now let's take a look at what the sleeper in ESP and Python integration might look like. We'll close this out. We'll take a look at those sleeper classes again, but this time with Python integration. You can see from our league class now that we are using a series of Python submit and Python get statements in order to interact with the Python API that already exists.
For getting our league information, we'd simply run a Python submit statement to define a league info variable. Then with that league info variable, we would use get_league in order to get the information for that variable and then send that variable back to JMP using Python get.
All of these functions work exactly the same as they did before with the JSL implementation we had of new HTTP request, except we no longer have that class because the HTTP request has been abstracted away, and it's happening on the Python level now. This makes for a lot simpler method definitions for our class. Now let's take a look at ESPN, which is the platform probably most of you are the most familiar with, and you can see here we have the same or similar classes for ESPN.
The way the Python API is built, though, this is more of an object-oriented approach where the league returns team objects, player objects, and so on and so forth. Then with those objects, we'll then use methods to access their attributes. I will run this class definition, so we can see a couple of examples of how we might get our data from ESPN and how we might interact with those objects, those class objects.
You'll see here I'll get my private league information. You see that that object has been defined. We will use get teams method on the league in order to get the team information. Now we have an associative array defined. We'll parse through this associative array using for each in order to print the names of all of the teams. As you can see here, this is the name of my team. If I want to access my team, I would subscript that teams variable to get my team. Now, if I want to run get wins on that team, I can see how many wins I had for that season seven, so pretty mediocre.
Now, if I wanted to look at the roster for that team, I would use the get roster function and then parse through that roster to return the player names., And you can see here, I would see all the players that I had rostered for that particular season. As I mentioned, these classes will be available to you. I took the time to comment them, I feel like, pretty well so that you can use them to access your own team information.
Now that we've got this data into JMP, in order to do some analysis on it, we need to get the data into JMP data tables. There are a couple of ways to do this, as I mentioned before, when you call data from an API, the data is usually returned to you in the form of a JSON string. There's actually a function built into JMP called JSON to data table, and we can use this function, as you can see from this example here in order to create a data table from our JSON string.
This is probably the simplest way of doing this. There's also a JSON import Wizard in JMP that you can use to customize what columns you're bringing in and what specific data you want from your table. Another way to do this, as you can imagine, might be more useful when working with our ESPN data since it was more object-oriented, is to define our table manually using JSL, and then to use the object methods in order to populate… Sorry. In order to populate those columns.
As you can see here, we are looping through team objects in order to populate the teams that are in a league. We're looping through team objects, and you can see here, in order to define the data for a particular column, we just let's use whatever method we need to populate that column. For the wins column, we would use get wins, losses, get losses, and so on and so forth, and if there's nested objects, we can loop through those objects in order to, say, populate a list of player IDs that we would use for a column.
You can see that for each expression there. Now let me show you how we've got these data tables in JMP. I have separate files for both getting sleeper data and getting ESPN data. You can see for the sleeper data, we're importing our classes because those are the methods that we use to get the data. I'll scroll through here pretty quickly, but I'll highlight the method I talked as we talked before, JSON to data table. You can see we use that here to define our columns and to create a data table for the teams in a league.
Again, I'll be sharing these files with you. Hopefully, if you have data in a sleeper league, all you need to do to get all of your data for every year that the league has existed is to run this final line right here, and it utilizes all these previously defined functions in this file, which you can take a look at in your own time. We have a similar thing for ESPN. You'll notice here we create our tables in a little bit different way, as you saw highlighted in the slide, where we define a table with JSL and then use object methods in order to populate those tables.
Again, hopefully, all you would need to do to get all of your ESPN data is to run this final line here. Let's take a look at what this data looks like in JMP. After running those files, you should be left with a directory that's titled by your league name. For each year of the league, you should have the following tables, the draft table, the members table, the teams table, as well as the individual rosters for each user in the league.
We'll take a look at those. You can see we have a list of the teams. Let me see if I can expand these columns. Hopefully, this is legible enough. For each team, you'll have the display name for the users. I'll make this a little bit smaller just so you can see a little better. For each team, you'll have the members of the league, the number of wins and losses they had. You can click on this players column and highlight the list of players for a particular roster.
We'll have a data table for the draft and the players that were drafted by a round and pick number. You can click on a user to see what individual player drafted in their draft, and we also have a table for the league members. You can imagine what these tables with the type of analysis that we might be able to do.
Before we jump into the analysis, I want to give you some motivation for the analysis as I previewed earlier. In 2022, I finished first in my league, while in 2023, I unfortunately finished last. I'm looking for some predictability and outcomes here, you can see, looking at the scatter plot on the left, I see that I had a top tight end and a top quarterback, whereas in the league that I finished last, I did not have either of those two positions as a top five position.
However, I did have better wide receiver, so looking at these two plots, I can't really tell exactly what caused me to have success in one year versus another, so that is the inspiration for my analysis. What are we going to look at today? The first thing we're going to look at is the fantasy league starts with the draft. There are certain pick spots in the draft that are better than others.
The next thing we're going to look at is what trends or changes have taken place in fantasy football over the last decade. What drafting strategies give you the best chance to win? A conservative or risky approach, and at the bottom there, I just have my league layout, which is pretty typical. One quarterback, two right receivers, two running backs, two flex spots, a kicker, and a defense with half PPR scoring. I think these are fairly standard league settings.
Hopefully, the takeaways that we have from our analysis can be used for your league as well. Does pick position impact success? We're going to work with those data tables that I showed you earlier in order to run some analysis. In order to do this, we're going to make use of a Workflow Builder in order to overview the steps that we take to organize the tables as well as perform the analysis.
You can take a look at the workflow here. We have this initial setup step, which just defines our league name, which just tells the workflow where to get the data from. I'll step through this initially and show you. We're doing the following steps for each year of the league. We're going to open a draft table. We're going to open a teams table.
Now for our draft table, we are going to subset by the number of users in the league to get the pick number that's associated with a particular user. Sorry about that, and that looks something like this. Now we're going to join this data table back into our teams table in order to get a table like this, which now has the number of wins that that player had, or that league member had, as well as where they picked in the draft. We're going to do this for every year of the league and concatenate the table in order to perform our analysis.
I'll run through the rest of those steps here. Some of these tables are popping up on a different screen, but I'm just going to show you the final output table as well as the analysis. As you can see in this plot here, we have our pick number on the X-axis, and on the Y-axis, we have both the number of wins that the team has as well as their final finishing position.
Looking at this graph, I don't see a huge discrepancy between or correlation between pick number and how well you finish. I'm looking at the wins down here. We can see maybe if you pick early in the draft, the mean number of wins over the course of my league history is just a little bit higher, and then there's maybe a falloff in the early middle of the draft. What I interpret from this is regardless of where you draft, it seems like you have a chance to win your tendency league.
Now let's look at how draft strategy has changed over the nine-year history of my league. I'll save a little bit of time I'm going to show you the steps that we're using. We're essentially opening all of our draft tables and then concatenating them and then doing some subsets, but to save time, I'll just run through this workflow and show you the output.
We have two output graphs here. We'll pay closer. I'll talk through this main one here, and you can take a look at this one on the side if you're interested. This is just a buy-around breakdown. Essentially, here we have the year on the X-axis and the number of players taken at a particular position in the top four rounds on the Y-axis.
Looking at this graph, we can see that the number of …this is league-wide, so the whole league, how many quarterbacks, running backs, tight-ins, and wide-receivers were taken in the top four rounds. We can see from 2015 up to 2020, there was an increase in the number of running backs taken, and this has fallen off decently in the last three years, so less running backs taken in the top four rounds. You can see why receiver had a similar trend to running backs, with it plateauing here and staying a little bit higher above running backs. You can see, quarterbacks have fallen off in draft, early draft popularity with a slight increase here lately.
Tight-end has stayed pretty steady with just a little bit of an increase here in the last couple of years. As you can see here in this graph, we have a round-by-round breakdown, and I'll share this to the community if you want to take a closer look at that one.
Now, we looked at how draft strategy has changed over the years, and now I want to look at, does a change draft strategy work? For this analysis, we're going to look at the number of players taken at each position over the first six rounds, and we're just going to look at this over the last three years. You can see the steps taken for this analysis here. I'm going to run that setup step. For each year, we're going to open up the draft table. We're going to open up a teams table. We're going to subset this draft table for the first six rounds. Then we're going to make a summary table that shows the number of players picked at each position for each user. We're going to do this for each year, and then you concatenate that in order to perform our analysis.
Let's first take a look at this Graph Builder plot here. We can see that the number of running backs taken in the first six rounds tends to be a little negatively correlated with the number of wins. That backs up the trend that we saw in our last analysis with the number of running backs being taken going down. We can see a peak in wide receivers with two being taken in the first six rounds with a little bit of a fall-off in wide receiver, a little bit of a fall-off, three wide receivers taken.
The steepest slope we see here is the tight end, which we'll come back to in a second, showing that picking a tight end in your first six rounds tends to have a fairly positive correlation with the number of wins you can expect to have on the season. Similarly, with quarterback, but slightly less steep line there. Let's look at this in a fairly more statistical way using a fit model. You can see here we have this very steep line that we can see for tight-end. Looking at our parameter estimates down here in the bottom left corner.
You can actually see with statistical significance, there's a fairly strong positive correlation between picking a tight-end in your first six traffics and the number of wins you can expect to have on a season. I would say that's probably the biggest takeaway from this analysis is while you should be picking less running backs, as you can see from this negative correlation, picking a tight end in the first six rounds is a fairly strong correlation to predict the number of wins that you have in a season.
Now I want to look at if top players are predictable year over year. More specifically, I want to see what percent of the top 12 fantasy producers by position are the same year over year and see if that gives you any insights on what players you should be focusing on in your draft.
Again, just to save a little bit of time here, I'm just going to run through this workflow. You can see the steps here on the side while this runs. While this runs, it takes just a little bit of time. I'll speak to the benefits of using workflows for your analysis. It allows you to step through iteratively the different steps of your analysis and try to figure out problem areas.
It's also if your coworkers have very easy access to the data that you're working with, it allows them to replicate your analysis and the steps to produce them very easily. I'll show you here the plots and the data that this analysis produced. You can look at the data over here on the side, and this essentially is telling you year over year what percent of the top 12 by each position was the same as the year before.
Looking at this graph, I would say that the number of running backs, this blue line here, is tending to fall off over the last few years. That means running back produces year over year are becoming a little bit less predictable compared to the previous year. While the right receivers are becoming a little bit more predictable as well as tight ends. A takeaway I'd have from this is the top running backs that we saw in 2023, for example, there was only a 33% carry over the last two years in the top 12 producers. Those positions just might be a little bit harder to predict than, say, wide receiver and tight end.
Our last analysis here. We'll take a look at who should we draft then in our draft. We take a look at all of these trends and all these patterns. What should our draft strategies be? For this analysis, we're going to take a look at draft value and how overperforming players based on their draft position impact your team's success. I do want to highlight this analysis just a little bit closer because I think it's fairly interesting.
After our setup step, we're going to, again, loop each year. We're going to first open a draft table, open our teams table. These next two steps you could ignore, they're just housekeeping things. Now we're going to run a bivariate analysis on our draft table to take a look at the relationship by position between the pick number for a particular player and the total points that they scored.
Taking a look at this by variable analysis, we have a breakdown by each position that shows here on the X-axis the pick number and then the total number of points that that player scored. I'm particularly interested in these plot points here that are above and below what they are expected or what the regression says the expected is for the position.
I want to see how these performances above and below the predicted linear fit affect our chances of winning. That's what the rest of this analysis is going to do. We're going to save out these regression values and these residuals from these plots to our draft table, as you can see here. Then we're going to group our rounds by early, middle, and late. Do a quick recode here for our round column.
Now we have a breakdown of early, middle, and late. We can see how over and under-performers by round category affect winning. I will run through the rest of this analysis. Give it a second while it populates. Apologies for the delay. We're almost there. Now you can see we're left with a data table that shows us it's a combined data table where all of the year's data is here. Now we have the number of extreme overperformers by around category for each user over the course of a particular year, and we have this data for all of our years. The point of this analysis is to see how extreme overperformers or underperformers in the particular rounds that they were drafted affect our chances to win.
We can take a look at this fit model plot here. This, I think, is the most interesting analysis of the day. We can take a look at some of the leverage plots here, paying particular attention to these extreme overperformers in the middle rounds and extreme underperformers in the early rounds. I'm looking at our parameter estimates here, we can see that with statistical significance, an over-performer in the middle rounds is a pretty positive correlation to predict the number of wins that you have on the season with a number of over-performers, having more over-performers in the middle rounds, giving you a better chance to have more fantasy wins.
Contrastingly, an extreme under-performer in the early rounds is a very negative correlation in the number of wins you can predict to have on the season. A takeaway from this would be to take shots in your shoot for the moon in your middle rounds. I'm going to try to draft those players that are home run hitters, for to say, and don't necessarily worry about missing on them. As you can see, getting an extreme under-performer in the middle round does not seem to be that punishing.
You might as well shoot for the moon there and try to get those players that give you league upside and the best chance to win, especially since you're not going to be punished that much for missing on them. Then contrastingly, missing on a player early in your draft and picking a player that's very injury-prone can be very detrimental to your fantasy football success.
Thank you for staying with me there through those analysis, and these are what I would say are some takeaways. Last year never repeats itself. As we saw for one of our analysis, the top fantasy producers from one season, especially for running backs, is a lot less predictable season to season.
In typical formats, running backs are becoming less valuable, and as I mentioned, less predictable, while wide receivers are becoming a little bit more valuable and are definitely more predictable than running backs. We can no longer neglect the QB and especially tight-end positions, as we saw, drafting a tight-end early in your draft tends to league itself to success. Early draft picks don't really win you a league, but they can lose it, and leagues are one in the middle rounds.
Take risks on high-upside players for the reasons that I discussed previously. Now it's your turn. I've supplied you guys with the classes and the API I use to import both sleeper and ESPN data into JMP. You can use these APIs to share your own analysis in the JMP community. If your fantasy league is not in Sleeper or ESPN, you can use the outline of Python integration in order to use any Python repos that you find in order to get that data into JMP and perform your analysis.
I want to thank you guys so much for your time here today. I'm really interested to see what analysis you guys produce, and hopefully, you guys can help me win my next fantasy football league. Thank you.