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 and the free program. Below, we calculate what would happen if her scores for the competition were replaced by the average of the other judges.
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.”
import pandas as pd
performances = pd.read_csv("../data/raw/performances.csv")
aspects = pd.read_csv("../data/raw/judged-aspects.csv")
scores = pd.read_csv("../data/raw/judge-scores.csv")
judge_goe = pd.read_csv("../data/processed/judge-goe.csv")
scores_with_context = scores.pipe(
pd.merge,
aspects,
on = "aspect_id",
how = "left"
).pipe(
pd.merge,
performances,
on = "performance_id",
how = "left"
).pipe(
pd.merge,
judge_goe,
on = [ "aspect_id", "judge" ],
how = "left"
).assign(
is_junior = lambda x: x["program"].str.contains("JUNIOR")
)
assert len(scores) == len(scores_with_context)
senior_scores = scores_with_context[~scores_with_context["is_junior"]].copy()
def total_points(row):
# In one edge-case, a "J6" is listed as providing a score of zero.
# (There was no sixth judge, and the minimum score for a component
# is 0.25, per ISU regulations.)
# In the if-clause below, we ignore this edge-case.
if (row["section"] == "components") and (row["score"] == 0):
return None
elif row["section"] == "elements":
return round(row["base_value"] + row["judge_goe"], 2)
elif row["section"] == "components":
return round(row["factor"] * row["score"], 2)
else:
print("Unknown section: {}".format(row["section"]))
return None
sk_amer_2016 = senior_scores[
(senior_scores["competition"] == "ISU GP 2016 Progressive Skate America") &
(senior_scores["program"].str.contains("MEN"))
].copy()
sk_amer_2016_without_abasova = sk_amer_2016[
(sk_amer_2016["judge"] != "J3")
].copy()
# The ISU's scoring system uses a trimmed mean, which removes the highest and
# lowest score for each element and component.
# To calculate the proper score for a competition, we do the same here.
def calc_trimmed_mean(score_list):
trimmed = sorted(score_list)[1:-1]
return round(sum(trimmed) / len(trimmed), 2)
def calculate_aspect_scores(aspect_judgments):
aspects_grp = aspect_judgments.groupby("aspect_id")
scores = pd.DataFrame({
"name": aspects_grp["name"].first(),
"program": aspects_grp["program"].first(),
"section": aspects_grp["section"].first(),
"performance_id": aspects_grp["performance_id"].first(),
"factor": aspects_grp["factor"].first(),
"base_value": aspects_grp["base_value"].first(),
"score": aspects_grp["score"].apply(lambda x: calc_trimmed_mean(x)),
"judge_goe": aspects_grp["judge_goe"].apply(lambda x: calc_trimmed_mean(x)),
"total_deductions": aspects_grp["total_deductions"].first()
})
return scores
def calculate_results(scores):
scores["total_points"] = scores.apply(total_points, axis=1)
perfs_grp = scores.groupby("performance_id")
perfs = pd.DataFrame({
"score": perfs_grp["total_points"].sum(),
"deductions": perfs_grp["total_deductions"].first(),
"program": perfs_grp["program"].first(),
"name": perfs_grp["name"].first()
})
perfs["total_score"] = perfs["score"] - perfs["deductions"]
comp_grp = perfs.groupby("name")
results = pd.DataFrame({
"final_score": comp_grp["total_score"].sum()
})
return results
def calc_trimmed_mean_with_average_judge(score_list):
average = sum(score_list) / len(score_list)
score_list = score_list.tolist() + [average]
trimmed = sorted(score_list)[1:-1]
return round(sum(trimmed) / len(trimmed), 2)
def calculate_score_with_average_judge(dataframe):
aspects = dataframe.groupby("aspect_id")
scores = pd.DataFrame({
"name": aspects["name"].first(),
"program": aspects["program"].first(),
"section": aspects["section"].first(),
"performance_id": aspects["performance_id"].first(),
"factor": aspects["factor"].first(),
"base_value": aspects["base_value"].first(),
"scores_of_panel": aspects["scores_of_panel"].first(),
"score": aspects["score"].apply(lambda x: calc_trimmed_mean_with_average_judge(x)),
"judge_goe": aspects["judge_goe"].apply(lambda x: calc_trimmed_mean_with_average_judge(x)),
"total_deductions": aspects["total_deductions"].first()
})
return scores
aspect_scores = calculate_aspect_scores(sk_amer_2016)
with_abasova = calculate_results(aspect_scores)\
.sort_values("final_score", ascending=False)
scores_without_abasova = calculate_score_with_average_judge(sk_amer_2016_without_abasova)
without_abasova = calculate_results(scores_without_abasova)\
.sort_values("final_score", ascending=False)
pd.merge(
with_abasova.reset_index(),
without_abasova.reset_index(),
on="name",
suffixes=["_actual", "_without"]
).set_index("name")\
.assign(
rank_actual = lambda x: x["final_score_actual"]\
.rank(ascending = False)\
.astype(int),
rank_without = lambda x: x["final_score_without"]\
.rank(ascending = False)\
.astype(int),
)
final_score_actual | final_score_without | rank_actual | rank_without | |
---|---|---|---|---|
name | ||||
Shoma UNO | 279.34 | 278.56 | 1 | 1 |
Jason BROWN | 268.38 | 267.30 | 2 | 2 |
Adam RIPPON | 261.43 | 261.71 | 3 | 3 |
Sergei VORONOV | 245.28 | 243.89 | 4 | 5 |
Boyang JIN | 245.08 | 244.94 | 5 | 4 |
Nam NGUYEN | 239.26 | 238.96 | 6 | 6 |
Maxim KOVTUN | 230.75 | 228.82 | 7 | 7 |
Timothy DOLENSKY | 226.53 | 226.53 | 8 | 8 |
Jorik HENDRICKX | 224.91 | 224.94 | 9 | 9 |
Brendan KERRY | 211.76 | 212.35 | 10 | 10 |