{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Example from the ISU GP 2016 Progressive Skate America\n", "\n", "Maira Abasova was the third judge (\"J3\") in the men's competition at the ISU GP 2016 Progressive Skate America for both the [short program](http://www.isuresults.com/results/season1617/gpusa2016/SEG001OF.HTM) and the [free program](http://www.isuresults.com/results/season1617/gpusa2016/SEG002OF.HTM). Below, we calculate what would happen if her scores for the competition were replaced by the average of the other judges.\n", "\n", "Abasova could not be reached, but BuzzFeed News sent a letter with detailed questions to her through the Figure Skating Federation of Russia. A federation representative declined to comment or make the judge available for an interview but said, “Abasova is aware of the letter and has no comment.”" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load scoring data" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "performances = pd.read_csv(\"../data/raw/performances.csv\")\n", "aspects = pd.read_csv(\"../data/raw/judged-aspects.csv\")\n", "scores = pd.read_csv(\"../data/raw/judge-scores.csv\")\n", "judge_goe = pd.read_csv(\"../data/processed/judge-goe.csv\")" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "scores_with_context = scores.pipe(\n", " pd.merge,\n", " aspects,\n", " on = \"aspect_id\",\n", " how = \"left\"\n", ").pipe(\n", " pd.merge,\n", " performances,\n", " on = \"performance_id\",\n", " how = \"left\"\n", ").pipe(\n", " pd.merge,\n", " judge_goe,\n", " on = [ \"aspect_id\", \"judge\" ],\n", " how = \"left\"\n", ").assign(\n", " is_junior = lambda x: x[\"program\"].str.contains(\"JUNIOR\")\n", ")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "assert len(scores) == len(scores_with_context)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "senior_scores = scores_with_context[~scores_with_context[\"is_junior\"]].copy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculating Scores of ISU GP Internationaux de France de Patinage 2017" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def total_points(row):\n", " # In one edge-case, a \"J6\" is listed as providing a score of zero.\n", " # (There was no sixth judge, and the minimum score for a component \n", " # is 0.25, per ISU regulations.)\n", " # In the if-clause below, we ignore this edge-case.\n", " if (row[\"section\"] == \"components\") and (row[\"score\"] == 0):\n", " return None\n", " \n", " elif row[\"section\"] == \"elements\":\n", " return round(row[\"base_value\"] + row[\"judge_goe\"], 2)\n", " \n", " elif row[\"section\"] == \"components\":\n", " return round(row[\"factor\"] * row[\"score\"], 2)\n", " \n", " else:\n", " print(\"Unknown section: {}\".format(row[\"section\"]))\n", " return None" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sk_amer_2016 = senior_scores[\n", " (senior_scores[\"competition\"] == \"ISU GP 2016 Progressive Skate America\") &\n", " (senior_scores[\"program\"].str.contains(\"MEN\"))\n", "].copy()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sk_amer_2016_without_abasova = sk_amer_2016[\n", " (sk_amer_2016[\"judge\"] != \"J3\")\n", "].copy()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# The ISU's scoring system uses a trimmed mean, which removes the highest and \n", "# lowest score for each element and component.\n", "# To calculate the proper score for a competition, we do the same here.\n", "def calc_trimmed_mean(score_list):\n", " trimmed = sorted(score_list)[1:-1]\n", " return round(sum(trimmed) / len(trimmed), 2)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def calculate_aspect_scores(aspect_judgments):\n", "\n", " aspects_grp = aspect_judgments.groupby(\"aspect_id\")\n", " \n", " scores = pd.DataFrame({\n", " \"name\": aspects_grp[\"name\"].first(),\n", " \"program\": aspects_grp[\"program\"].first(),\n", " \"section\": aspects_grp[\"section\"].first(),\n", " \"performance_id\": aspects_grp[\"performance_id\"].first(),\n", " \"factor\": aspects_grp[\"factor\"].first(),\n", " \"base_value\": aspects_grp[\"base_value\"].first(),\n", " \"score\": aspects_grp[\"score\"].apply(lambda x: calc_trimmed_mean(x)),\n", " \"judge_goe\": aspects_grp[\"judge_goe\"].apply(lambda x: calc_trimmed_mean(x)),\n", " \"total_deductions\": aspects_grp[\"total_deductions\"].first()\n", " })\n", " \n", " return scores" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def calculate_results(scores):\n", " \n", " scores[\"total_points\"] = scores.apply(total_points, axis=1)\n", " \n", " perfs_grp = scores.groupby(\"performance_id\")\n", " \n", " perfs = pd.DataFrame({\n", " \"score\": perfs_grp[\"total_points\"].sum(),\n", " \"deductions\": perfs_grp[\"total_deductions\"].first(),\n", " \"program\": perfs_grp[\"program\"].first(),\n", " \"name\": perfs_grp[\"name\"].first()\n", " })\n", " \n", " perfs[\"total_score\"] = perfs[\"score\"] - perfs[\"deductions\"]\n", " \n", " comp_grp = perfs.groupby(\"name\")\n", " \n", " results = pd.DataFrame({\n", " \"final_score\": comp_grp[\"total_score\"].sum()\n", " })\n", " \n", " return results" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def calc_trimmed_mean_with_average_judge(score_list):\n", " average = sum(score_list) / len(score_list)\n", " score_list = score_list.tolist() + [average]\n", " trimmed = sorted(score_list)[1:-1]\n", " return round(sum(trimmed) / len(trimmed), 2)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def calculate_score_with_average_judge(dataframe):\n", "\n", " aspects = dataframe.groupby(\"aspect_id\")\n", " \n", " scores = pd.DataFrame({\n", " \"name\": aspects[\"name\"].first(),\n", " \"program\": aspects[\"program\"].first(),\n", " \"section\": aspects[\"section\"].first(),\n", " \"performance_id\": aspects[\"performance_id\"].first(),\n", " \"factor\": aspects[\"factor\"].first(),\n", " \"base_value\": aspects[\"base_value\"].first(),\n", " \"scores_of_panel\": aspects[\"scores_of_panel\"].first(),\n", " \"score\": aspects[\"score\"].apply(lambda x: calc_trimmed_mean_with_average_judge(x)),\n", " \"judge_goe\": aspects[\"judge_goe\"].apply(lambda x: calc_trimmed_mean_with_average_judge(x)),\n", " \"total_deductions\": aspects[\"total_deductions\"].first()\n", " })\n", " \n", " return scores" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "aspect_scores = calculate_aspect_scores(sk_amer_2016)\n", "with_abasova = calculate_results(aspect_scores)\\\n", " .sort_values(\"final_score\", ascending=False)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "scores_without_abasova = calculate_score_with_average_judge(sk_amer_2016_without_abasova)\n", "without_abasova = calculate_results(scores_without_abasova)\\\n", " .sort_values(\"final_score\", ascending=False)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " | final_score_actual | \n", "final_score_without | \n", "rank_actual | \n", "rank_without | \n", "
---|---|---|---|---|
name | \n", "\n", " | \n", " | \n", " | \n", " |
Shoma UNO | \n", "279.34 | \n", "278.56 | \n", "1 | \n", "1 | \n", "
Jason BROWN | \n", "268.38 | \n", "267.30 | \n", "2 | \n", "2 | \n", "
Adam RIPPON | \n", "261.43 | \n", "261.71 | \n", "3 | \n", "3 | \n", "
Sergei VORONOV | \n", "245.28 | \n", "243.89 | \n", "4 | \n", "5 | \n", "
Boyang JIN | \n", "245.08 | \n", "244.94 | \n", "5 | \n", "4 | \n", "
Nam NGUYEN | \n", "239.26 | \n", "238.96 | \n", "6 | \n", "6 | \n", "
Maxim KOVTUN | \n", "230.75 | \n", "228.82 | \n", "7 | \n", "7 | \n", "
Timothy DOLENSKY | \n", "226.53 | \n", "226.53 | \n", "8 | \n", "8 | \n", "
Jorik HENDRICKX | \n", "224.91 | \n", "224.94 | \n", "9 | \n", "9 | \n", "
Brendan KERRY | \n", "211.76 | \n", "212.35 | \n", "10 | \n", "10 | \n", "