JMP 的 Python 集成功能得到增强,现在导入您的梦幻橄榄球联赛数据比以往任何时候都更加轻松。本次演讲首先简要介绍如何使用 JMP 的原生 HTTP 请求功能访问 ESPN 和 Sleeper 等梦幻橄榄球服务提供商提供的 API,然后重点展示如何使用 Python 社区现有的 API 封装库来提取多年来的梦幻联赛数据。从联赛排名、季后赛结果、NFL 球员统计数据和梦幻橄榄球比赛结果,到选秀中的高光时刻和低谷时刻,海量数据等待您去探索!
成功主要取决于选秀眼光,还是全年持续的交易和引援?球员的爆发性梦幻表现能否年复一年地重复?拥有队内排名前三的梦幻得分手有多重要?跑卫仍然是梦幻联赛中最具影响力的位置吗?还是外接手和四分卫的地位已经上升?我们将探讨这些问题,看看JMP能否助您赢得下一个联赛。
大家好,我是Nick Holmes,JMP公司的测试工程师。感谢各位今天收看我的节目。今天,我们将探讨如何使用JMP来赢得你的梦幻橄榄球联赛。我这里用了“可能”这个词,因为显然,梦幻橄榄球比赛没有任何绝对的保证。今天,我们将学习如何将梦幻橄榄球数据导入JMP,进行一些分析,看看我们能发现什么。
简单介绍一下我自己。我自认为是个狂热的梦幻橄榄球爱好者。我管理着一个12人重选联赛,这个联赛从2015年左右就开始了。我们今天举例和分析主要用的就是这个联赛的数据。我做这个演讲或者说这个项目的初衷是,我在2022年赢得了联赛冠军,但在2023年却垫底了。我只是想找到一些预测方法或者分析手段,帮助我预测在梦幻橄榄球中的结果。
我不是统计学家,但我会尽我所能。我们会使用来自 ESPN 和 Sleeper 的数据。ESPN,大家可能都很熟悉,它是一家梦幻橄榄球服务提供商。Sleeper 也一样,只不过它是一个不同的梦幻橄榄球联赛托管平台。我们会从这两个平台导入数据,并用于我们的分析。
我从 2015 年到 2020 年左右使用 SPN,从 2021 年至今使用 Sleeper,所以我们需要从这两个平台获取数据。下面简要概述一下我们今天将如何把这些数据导入 JMP 并进行分析。首先,我们需要使用 API 来导入数据。我们将通过两种方式实现,一种是使用新建 HTTP 请求,另一种是使用 JMP-Python 集成。
接下来,我们将整理这些请求,或者说我将向您展示如何将它们整理成 JMP 类或 JMP 类对象。一旦我们将这些数据导入 JMP,就需要将其整理成 JMP 数据表。有几种方法可以做到这一点,我将概述 JMP 原生的 JSON 转数据表功能,以及使用关联数组和 JSL 创建 JMP 数据表的方法。
一旦我们将数据导入到 JMP 数据表中,接下来我们将进行一些探索和分析。首先,我们将对数据表进行一些操作。我将使用 Workflow Builder 来演示,然后我们将使用 JMP 的一些平台进行可视化和分析。
好的,那么什么是 API 呢?您可以在旁边看到 API 的定义,但我简单来说,API 是一种从 Web 服务器检索数据的方式。HTTP 协议包含一些方法,例如 GET、POST、DELETE 和 PUT,但今天我们将重点介绍最简单的 GET 方法。我们将向 URL 或 API 端点发送 GET 请求,然后服务器会返回一个 JSON 字符串,我们将使用该字符串在 JMP 中创建关联数组以及创建 JMP 数据表。
JMP 中的“新建 HTTP 请求”功能允许我们使用 JSL 与 REST API 进行通信。正如我之前提到的,该功能支持使用 GET、POST、PUT 和 DELETE 等请求方法。今天,我们将主要关注 GET 请求,并了解如何使用“新建 HTTP 请求”将数据导入 JMP 是多么简单。
我首先想向你展示如何从梦幻联赛Sleeper获取数据。如果你快速在谷歌上搜索他们的API,你会找到一些相当不错的文档,其中详细说明了要使用的HTTP请求端点以及你可以预期收到的数据类型。
实际上,这是使用 API 的最佳情况:提供商提供非常清晰的 API 说明,以及返回的数据类型,并提供非常详尽的文档。正如您从他们的文档中看到的,它概述了如何获取联赛数据,以及如何将球员名单数据导入 JMP。看起来,对于大多数情况,我们只需要在 URL 中提供联赛 ID 即可。
在这个例子中,我仅概述了它在 JMP 中的大致流程。我们提供自己的联赛 ID 并将其定义为 URL 变量。然后,我们使用新的 HTTP 请求发送该请求,并将 URL 和 GET 方法传递给它。发送请求后,我们解析返回给 JMP 的 JSON 数据。
现在我们已经了解了这个请求的大致形式,接下来看看它在 JMP 中是如何运行的。可以看到这里打开了一个 JMP 脚本窗口。如果你看一下定义的 URL,可以看到其中包含了我们的联赛 ID 和你在幻灯片中看到的请求。如果我们在这里运行它,你会看到返回的是一个 JSON 字符串。为了更好地在 JMP 中处理这些数据,我们需要将其转换为 JMP 关联数组,这就是这里 `Parsed JSON` 函数的作用。
您可以看到,我们得到了一个关联数组。如果我们查看这个关联数组,然后使用下标来获取联赛名称,可以看到我们返回的是“league-certified fantasy football”,也就是联赛名称。现在我已经向您展示了一个非常简单的演示,接下来,我想向您展示如何将查询操作或组织成 JMP 类,以便组织我们的请求,也就是 HTTP 请求。让我们结束这个演示,打开项目文件。查看这个项目文件,您会看到我们定义了一些类来组织查询。
我们有这样一个联赛类。正如您从这里的参考文档中看到的,它实际上是从一个 Python API 中借鉴而来,我借用了它的框架,稍后在讨论 JMP Python 集成时我们会更详细地介绍它。只需看一下这个类,您就会发现它包含了联赛用户、玩家、选秀和统计数据等类。所有这些后端类都在调用这个基于超级类的 API,而正是这个 API 在后台发起了新的 HTTP 请求。
我们来看看如何使用这个类来获取数据。我将运行这个类定义,然后向下滚动到页面底部,那里有一些示例。如果滚动让您感到头晕,请见谅。现在您可以看到我正在定义一个联赛对象。同样,我为它提供了联赛 ID。您可以看到这里有类定义,现在我们可以使用为该对象定义的方法来获取联赛的球员名单。我们可以获取联赛的用户,等等。所有这些数据都以关联数组的形式返回。
我们暂时先不仔细查看具体内容,只向您展示我们获取到的数据。现在,我们对用户、玩家和窗帘都进行了相同的操作。我会将所有这些内容连同幻灯片一起作为演示材料分享给 JMP 社区。您应该能够使用我稍后演示的类将数据导入 JMP。
我们发现,从 Sleeper 获取数据非常容易。他们提供了一个定义完善的 API,我们可以用它来创建 HTTP 请求。但 ESPN 的情况似乎并非如此。如果你快速搜索一下 ESPN API 或 ESPN Fantasy API,你会发现唯一能找到的只是别人编写的一个 Python 类。他们的 API 似乎没有公开的文档。
我们该怎么办?现在正是和大家聊聊 JMP 的好时机,JMP 18 新增了 Python 集成。JMP 18 已经预装了 Python。我们只需极少的设置就能使用 Python 包。之前我向大家展示的 Sleeper 类,或者说受 Python 类启发而来的类,现在无需我们自己编写类和方法定义,只需使用您在这里看到的这行脚本安装 Python 包,然后通过 Python 交互将数据导入 JMP 即可。
既然 ESPN 已经有了这样的功能,我们现在也可以对 ESPN 做同样的事情。由于 ESPN 缺乏完善的文档,我们可以直接使用其他人已经编写好的 Python API。使用 Python 集成有哪些好处呢?所有繁琐的工作都已经完成。我们无需编写 API 调用逻辑或任何其他类型的数据操作代码。我们只需依赖其他人用 Python 编写的函数和文档即可。
我们只需调用 Python 函数并将数据发送回 JMP 即可。以下是一个简单的示例。如果可能不太容易看清,敬请谅解,因为我想把所有内容都显示在屏幕上。这些幻灯片稍后会提供给您查看,但您也可以通过查看函数定义的大小来了解其结构。相比使用 Python 集成来调用别人已经用 Python 定义的 `get_scoreboard` 函数,自己编写一个获取记分板的函数要复杂得多。
现在我们来看看ESP和Python集成中的“睡眠者”功能可能是什么样子。我们将结束今天的讨论。我们会再次查看这些“睡眠者”类,但这次是集成Python。从我们的联赛类中可以看到,我们使用了一系列Python的submit和get语句来与现有的Python API进行交互。
为了获取联赛信息,我们只需运行一条 Python submit 语句来定义一个联赛信息变量。然后,使用该联赛信息变量,我们使用 get_league 函数获取该变量对应的联赛信息,最后使用 Python get 函数将该变量返回给 JMP。
所有这些函数的工作方式与之前使用 JSL 实现的新 HTTP 请求完全相同,只是我们不再需要那个类,因为 HTTP 请求已被抽象化,现在它在 Python 层处理。这使得我们类的方法定义更加简洁。现在让我们来看看 ESPN,这可能是你们大多数人最熟悉的平台,可以看到我们为 ESPN 提供了相同或类似的类。
不过,Python API 的构建方式更偏向于面向对象,联盟会返回球队对象、球员对象等等。然后,我们将使用这些对象的方法来访问它们的属性。我将运行这个类定义,以便我们可以看到一些示例,了解如何从 ESPN 获取数据以及如何与这些对象(即类对象)进行交互。
你会看到我在这里获取我的私人联赛信息。可以看到这个对象已经定义了。我们将使用联赛的 `getTeams` 方法来获取球队信息。现在我们已经定义了一个关联数组。我们将使用 `for Each` 循环遍历这个关联数组,以便打印所有球队的名称。正如你在这里看到的,这是我的球队名称。如果我想访问我的球队,我会使用 `teams` 变量的下标来获取我的球队。现在,如果我想运行 `getWins` 方法来获取该球队的胜场数,我可以看到我在该赛季的胜场数是七场,相当平庸。
现在,如果我想查看该队的球员名单,我会使用 `get roster` 函数,然后解析该名单以返回球员姓名。您可以看到,这里列出了我在该赛季加入球队的所有球员。正如我提到的,这些类对您是可用的。我花时间为它们添加了注释,我认为注释得相当清楚,以便您可以使用它们来访问您自己的球队信息。
现在我们已经将数据导入到 JMP 中,为了进行分析,我们需要将数据导入到 JMP 数据表中。正如我之前提到的,有几种方法可以做到这一点。当您从 API 调用数据时,数据通常以 JSON 字符串的形式返回。JMP 内置了一个名为“JSON to data table”(JSON 转数据表)的函数,我们可以使用此函数(如本例所示)根据 JSON 字符串创建数据表。
这可能是最简单的方法了。JMP 中还有一个 JSON 导入向导,你可以用它来自定义要导入的列以及想要从表中获取的具体数据。另一种方法,正如你所想,在处理我们的 ESPN 数据时可能更有用,因为它更偏向于面向对象:使用 JSL 手动定义表,然后使用对象方法来填充……抱歉,是填充这些列。
正如您在这里看到的,我们正在遍历球队对象,以便填充联赛中的所有球队。我们遍历球队对象,并且可以看到,为了定义特定列的数据,我们只需使用所需的方法来填充该列即可。例如,对于胜场数列,我们将使用 `get wins`、`get loss` 等方法。如果存在嵌套对象,我们可以遍历这些对象,例如,填充一个球员 ID 列表,用于填充某个列。
你可以看到每个表达式的情况。现在让我来演示一下我们在 JMP 中是如何创建这些数据表的。我分别创建了用于获取 Sleeper 数据和 ESPN 数据的单独文件。你可以看到,对于 Sleeper 数据,我们导入了类,因为这些类是我们用来获取数据的方法。我会快速浏览一下,但我会重点介绍之前提到的方法:JSON 转数据表。你可以看到,我们在这里使用它来定义列,并为联赛中的球队创建一个数据表。
我会再次与您分享这些文件。如果您有某个冷门联赛的数据,只需运行这里的最后一行代码,即可获取该联赛自成立以来每年的所有数据。该代码会用到文件中所有已定义的函数,您可以稍后自行查看。ESPN 的数据处理方式类似。您会注意到,我们创建表格的方式略有不同,正如您在幻灯片中看到的,我们使用 JSL 定义表格,然后使用对象方法来填充这些表格。
希望您只需运行最后这行命令就能获取所有 ESPN 数据。我们来看看这些数据在 JMP 中的显示效果。运行这些文件后,您应该会得到一个以联赛名称命名的目录。对于联赛的每个年份,您应该会看到以下表格:选秀表、成员表、球队表,以及联赛中每个用户的个人阵容表。
我们来看看这些数据。您可以看到这里有一个球队列表。我看看能不能把这些列展开。希望现在足够清晰易读。每个球队旁边都会显示用户的显示名称。我把字体缩小一点,方便您看得更清楚。每个球队旁边还会显示联赛成员、胜负场次。您可以点击“球员”列,查看特定球队的球员列表。
我们会提供选秀数据表,列出每轮选秀中被选中的球员及其顺位。您可以点击用户查看其选秀中被选中的具体球员,我们还有一份联盟成员的选秀表格。您可以想象一下,有了这些表格,我们可以进行哪些类型的分析。
在深入分析之前,我想先解释一下我进行这项分析的动机,就像我之前预告的那样。2022年,我所在的联赛排名第一,而2023年,不幸的是,我排名垫底。我希望从中发现一些可预测的结果。正如你所看到的,从左侧的散点图中可以看出,我拥有一名顶尖的近端锋和一名顶尖的四分卫,而在我排名垫底的那个联赛中,这两个位置都没有进入前五。
不过,我的外接手确实更好,所以从这两张图表来看,我无法确切地判断是什么原因导致我某一年取得成功,而另一年却失败了,这就是我进行分析的灵感来源。我们今天要看什么呢?首先,我们要看的是,梦幻联赛是从选秀开始的。选秀中某些顺位比其他顺位更有优势。
接下来我们要探讨的是过去十年梦幻橄榄球发生了哪些趋势或变化。哪些选秀策略能最大程度地提高胜率?是保守策略还是冒险策略?最后,我还要介绍一下我的联赛配置,这相当典型:一名四分卫、两名右外接手、两名跑卫、两个灵活位置、一名踢球手,以及一支采用半PPR计分制的防守组。我认为这些都是相当标准的联赛设置。
希望我们分析得出的结论也能对你们的联赛有所帮助。选秀顺位会影响比赛结果吗?我们将使用之前展示的数据表进行一些分析。为此,我们将使用工作流构建器来概览整理数据表和执行分析的步骤。
您可以在这里查看工作流程。我们首先进行初始设置,定义联赛名称,并告知工作流程从哪里获取数据。我将逐步演示给您看。接下来,我们将针对联赛的每一年执行以下步骤。我们将打开一个选秀表。我们将打开一个球队表。
现在,对于我们的选秀表,我们将按联盟用户数量进行子集划分,以获取与特定用户关联的选秀顺位。抱歉,结果大致如下。接下来,我们将把这个数据表与我们的球队表连接起来,得到一个类似这样的表格,其中包含该球员或联盟成员的胜场数以及他们在选秀中的顺位。我们将对联盟的每一年都进行此操作,并将表格连接起来以进行分析。
接下来我会详细介绍剩余的步骤。有些表格会在另一个屏幕上显示,但我这里只展示最终的输出表格和分析结果。正如您在这张图中看到的,X轴代表我们的预测号码,Y轴代表球队的胜场数和最终排名。
从这张图表来看,我并没有发现选秀顺位和最终成绩之间存在显著差异或相关性。我主要关注的是胜场数。我们可以看到,如果你在选秀初期就选择靠前的顺位,那么在我联赛的历史数据中,平均胜场数可能会略高一些,然后在选秀中后期可能会出现下滑。我的理解是,无论你的选秀顺位如何,你都有机会赢得你所在联赛的冠军。
现在我们来看看在我所在的联盟九年的历史中,选秀策略发生了哪些变化。为了节省时间,我将直接展示我们使用的步骤。我们基本上是打开所有的选秀表格,然后将它们合并,再进行一些子集处理。为了节省时间,我将直接演示整个流程并展示结果。
这里有两个输出图表。我们仔细看看。我先讲解一下这个主要的图表,如果你感兴趣,可以看看旁边的那个。这只是一个选秀顺位细分图表。简单来说,X轴代表年份,Y轴代表在前四轮选秀中特定位置被选中的球员人数。
从这张图表可以看出,这是全联盟的数据,也就是整个联盟在前四轮选秀中选中的四分卫、跑卫、近端锋和外接手的数量。我们可以看到,从2015年到2020年,跑卫的选秀人数有所增加,但在过去三年里有所下降,也就是说,在前四轮选秀中被选中的跑卫数量减少了。外接手的选秀趋势与跑卫类似,其选秀人数在此趋于稳定,略高于跑卫。我们可以看到,四分卫在选秀早期的受欢迎程度有所下降,但最近略有回升。
近几年,近端锋的数量一直保持稳定,只有略微增长。正如你在这张图表中看到的,我们有逐轮的详细数据,如果你想更深入地了解,我可以把这张图表分享给社区。
现在,我们已经了解了选秀策略这些年来的变化,接下来我想探讨的是,改变选秀策略是否有效?为了进行这项分析,我们将考察过去三年中前六轮选秀中每个位置被选中的球员人数。您可以在这里看到分析步骤。我将运行设置步骤。对于每一年,我们将打开选秀表格,并打开球队表格。我们将筛选出前六轮的选秀数据。然后,我们将创建一个汇总表,显示每个用户在每个位置上选中的球员人数。我们将对每一年都进行此操作,然后将结果合并以进行分析。
我们先来看一下这张图表。可以看出,前六轮选秀中被选中的跑卫数量与球队的胜场数呈轻微负相关。这印证了我们上次分析中观察到的趋势,即跑卫被选中的数量呈下降趋势。我们还可以看到外接手的数量在前六轮达到峰值,有两名外接手被选中,之后略有下降,最后有三名外接手被选中。
这里我们看到斜率最陡的是近端锋,我们稍后会再详细讨论。这表明,在前六轮选秀中选择近端锋与赛季预期胜场数往往呈相当正相关。四分卫的情况也类似,但斜率略低一些。让我们用拟合模型,以更统计学的方式来分析这个问题。可以看到,近端锋的斜率非常陡峭。看看左下角的参数估计值。
实际上,从统计学角度来看,在前六轮选秀中选择近端锋与赛季预期胜场数之间存在相当强的正相关性。我认为这项分析最重要的结论是:虽然你应该减少选择跑卫,正如你从这种负相关性中看到的那样,但在前六轮选择近端锋与预测赛季胜场数之间存在相当强的相关性。
现在我想看看顶级球员的表现是否具有可预测性。更具体地说,我想看看每个位置排名前 12 的梦幻球员中,每年同场竞技的球员所占的百分比是多少,并以此来判断你在选秀时应该重点关注哪些球员。
为了节省时间,我将直接演示这个工作流程。您可以在旁边看到运行过程中的步骤。运行过程只需一点时间。接下来,我将讲解使用工作流程进行分析的优势。它允许您逐步迭代地完成分析的各个步骤,并尝试找出问题所在。
此外,如果你的同事能够非常方便地访问你正在处理的数据,他们就能轻松地复现你的分析及其步骤。我将在这里展示该分析生成的图表和数据。你可以查看旁边的数据,这些数据主要显示了每年排名前 12 的选手中,每个位置的选手与前一年相同的百分比。
从这张图表来看,我认为跑卫的数量(图中蓝线所示)在过去几年里呈下降趋势。这意味着与前一年相比,跑卫的年产量预测性有所降低。而外接手和近端锋的预测性则有所提高。例如,在2023年,我们看到的顶级跑卫中,过去两年排名前12位的球员的持球冲锋次数仅占总冲球次数的33%。这些位置的球员可能比外接手和近端锋更难预测。
这是我们的最后一个分析。接下来,我们将探讨选秀时应该选择哪些球员。我们会分析所有这些趋势和模式。我们的选秀策略应该是什么?本次分析将着重考察选秀价值,以及那些选秀顺位过高但表现超出预期的球员如何影响球队的战绩。我想更详细地介绍一下这个分析,因为它相当有趣。
设置完成后,我们将再次重复每年的操作。首先,我们将打开选秀表和球队表。接下来的两步可以忽略,它们只是一些准备工作。现在,我们将对选秀表进行双变量分析,以查看特定球员的选秀顺位与其总得分之间的关系。
通过变量分析,我们可以按位置进行细分,X轴显示的是球员的选秀顺位,Y轴显示的是该球员的总得分。我特别关注图中那些高于和低于预期值(或回归分析得出的该位置预期值)的点。
我想看看这些高于和低于预测线性拟合值的表现会如何影响我们的获胜几率。这就是接下来分析的目的。我们会把这些回归值和残差从这些图表中保存到我们的选秀表中,就像你在这里看到的。然后我们会把选秀轮次按早期、中期和后期分组。这里快速重新编码一下轮次列。
现在我们已经有了早期、中期和晚期的细分数据。我们可以看到,按轮次类别划分的超常发挥和表现不佳的球员如何影响我们的胜率。接下来我会继续分析剩余部分。请稍等片刻,数据正在加载中。抱歉耽搁了。我们快完成了。现在您可以看到,我们得到了一个汇总数据表,其中包含了全年的所有数据。现在,我们统计了每个用户在特定年份中按轮次类别划分的超常发挥球员的数量,并且我们拥有所有年份的此类数据。这项分析的目的是了解在特定轮次中表现超常或表现不佳的球员如何影响我们的胜率。
我们可以看一下这个拟合模型图。我认为这是今天最有趣的分析。我们可以看一下这里的一些杠杆图,特别关注中期轮次表现极佳的球员和早期轮次表现极差的球员。我正在查看这里的参数估计值,我们可以看到,统计上显著,中期轮次表现极佳的球员与预测赛季胜场数之间存在相当强的正相关性。中期轮次表现极佳的球员越多,你赢得更多梦幻联赛胜利的几率就越大。
相反,在选秀初期选到表现极差的球员,与你预测的赛季胜场数呈非常负面的相关性。由此可见,在选秀中期,你应该大胆尝试,争取选到那些能打出全垒打的球员,而不必过于担心选错。正如你所见,在选秀中期选到表现极差的球员似乎并不会造成太大的损失。
你不妨放手一搏,争取选中那些能提升联赛排名、最大限度增加夺冠几率的球员,毕竟即使没选到他们,也不会受到太大的惩罚。相反,如果在选秀初期就错过了某个球员,或者选中了一个极易受伤的球员,那么你的梦幻橄榄球之旅将会遭遇巨大的挫折。
感谢您耐心看完这些分析,以下是我的一些总结。去年的情况永远不会重演。正如我们在之前的分析中所看到的,上赛季的顶级梦幻球员,尤其是跑卫,每个赛季的预测难度都会大大增加。
在典型的联赛模式中,跑卫的价值正在下降,而且正如我之前提到的,他们的表现也越来越难以预测;而外接手的价值则略有上升,而且他们的表现也比跑卫更加稳定。我们不能再忽视四分卫,尤其是近端锋的位置。正如我们所见,在选秀初期选择一名近端锋往往能提高联赛的胜率。选秀初期的顺位并不能保证你赢得联赛冠军,但却可能导致你输掉比赛,而联赛的胜负往往在中段轮次才真正见分晓。
出于我之前讨论过的原因,要敢于冒险选择高潜力球员。现在轮到你们了。我已经提供了我用来将 Sleeper 和 ESPN 数据导入 JMP 的类和 API。你们可以使用这些 API 在 JMP 社区分享自己的分析。如果你们的梦幻联赛没有使用 Sleeper 或 ESPN,你们可以参考 Python 集成的概要,使用任何找到的 Python 代码库将数据导入 JMP 并进行分析。
非常感谢各位今天抽出时间来这里。我很期待看到你们的分析,希望你们能帮助我赢得下一场梦幻橄榄球联赛。谢谢!