{ "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", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
final_score_actualfinal_score_withoutrank_actualrank_without
name
Shoma UNO279.34278.5611
Jason BROWN268.38267.3022
Adam RIPPON261.43261.7133
Sergei VORONOV245.28243.8945
Boyang JIN245.08244.9454
Nam NGUYEN239.26238.9666
Maxim KOVTUN230.75228.8277
Timothy DOLENSKY226.53226.5388
Jorik HENDRICKX224.91224.9499
Brendan KERRY211.76212.351010
\n", "
" ], "text/plain": [ " final_score_actual final_score_without rank_actual \\\n", "name \n", "Shoma UNO 279.34 278.56 1 \n", "Jason BROWN 268.38 267.30 2 \n", "Adam RIPPON 261.43 261.71 3 \n", "Sergei VORONOV 245.28 243.89 4 \n", "Boyang JIN 245.08 244.94 5 \n", "Nam NGUYEN 239.26 238.96 6 \n", "Maxim KOVTUN 230.75 228.82 7 \n", "Timothy DOLENSKY 226.53 226.53 8 \n", "Jorik HENDRICKX 224.91 224.94 9 \n", "Brendan KERRY 211.76 212.35 10 \n", "\n", " rank_without \n", "name \n", "Shoma UNO 1 \n", "Jason BROWN 2 \n", "Adam RIPPON 3 \n", "Sergei VORONOV 5 \n", "Boyang JIN 4 \n", "Nam NGUYEN 6 \n", "Maxim KOVTUN 7 \n", "Timothy DOLENSKY 8 \n", "Jorik HENDRICKX 9 \n", "Brendan KERRY 10 " ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.merge(\n", " with_abasova.reset_index(),\n", " without_abasova.reset_index(),\n", " on=\"name\",\n", " suffixes=[\"_actual\", \"_without\"]\n", ").set_index(\"name\")\\\n", " .assign(\n", " rank_actual = lambda x: x[\"final_score_actual\"]\\\n", " .rank(ascending = False)\\\n", " .astype(int),\n", " rank_without = lambda x: x[\"final_score_without\"]\\\n", " .rank(ascending = False)\\\n", " .astype(int),\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "---\n", "\n", "---" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.0" } }, "nbformat": 4, "nbformat_minor": 2 }