on_rankings / app.py
m7n's picture
Update app.py
1862231
raw
history blame
17.1 kB
# -*- coding: utf-8 -*-
"""ranking_simulation_0.ipynb
Automatically generated by Colaboratory.
Original file is located at
https://colab.research.google.com/drive/1GzjewA8ePLJ1fdk76Qax-aRRsSsNp417
"""
# Commented out IPython magic to ensure Python compatibility.
# %%capture
# !pip install gradio
# !pip install opinionated
#
import numpy as np
import pandas as pd
import opinionated
import matplotlib.pyplot as plt
plt.style.use("opinionated_rc")
from opinionated.core import download_googlefont
download_googlefont('Quicksand', add_to_cache=True)
plt.rc('font', family='Quicksand')
# import colormaps as cmaps
def simulate_applicant_judging(num_applicants, num_judges, ratings_per_applicant, alpha, beta,
judge_error=1, judgment_coarse_graining=False):
"""
Simulates judging of applicants by a set of judges with coarse graining functionality and random tie-breaking in ranking.
:param num_applicants: Number of applicants to be judged.
:param num_judges: Number of judges available for judging.
:param ratings_per_applicant: Number of ratings each applicant should receive.
:param alpha: Alpha parameter for the Beta distribution.
:param beta: Beta parameter for the Beta distribution.
:param judge_error_std_dev: Standard deviation for the judge's margin of error.
:param judgment_coarse_graining: Number of buckets for coarse graining (2 to 100) or False to disable.
:return: A Pandas DataFrame with detailed results for each applicant.
"""
# Generate the quality of applicants from a Beta distribution normalized to 0-100
applicant_qualities = np.random.beta(alpha, beta, num_applicants) * 100
# Function to apply coarse graining
def coarse_grain_evaluation(evaluation, grain_size):
return round(evaluation / (100 / grain_size)) * (100 / grain_size)
# Initialize evaluations dictionary
evaluations = {f"Applicant_{i+1}": [] for i in range(num_applicants)}
judge_workload = np.zeros(num_judges)
# Randomly assign judges to applicants
for _ in range(ratings_per_applicant):
for applicant in range(num_applicants):
probabilities = (max(judge_workload) - judge_workload + 1) / sum(max(judge_workload) - judge_workload + 1)
judge = np.random.choice(num_judges, p=probabilities)
judge_workload[judge] += 1
evaluation = np.random.uniform(applicant_qualities[applicant]-judge_error, applicant_qualities[applicant]+judge_error)
# Apply coarse graining if enabled
if judgment_coarse_graining:
evaluation = coarse_grain_evaluation(evaluation, judgment_coarse_graining)
evaluations[f"Applicant_{applicant+1}"].append(evaluation)
# Prepare data for DataFrame
data = []
for applicant, (quality, scores) in enumerate(zip(applicant_qualities, evaluations.values()), 1):
average_evaluation = np.mean(scores)
original_ranks = np.argsort(np.argsort(-np.array(scores))) + 1
data.append([f"Applicant_{applicant}", quality, average_evaluation, scores, list(original_ranks)])
# Create DataFrame
df = pd.DataFrame(data, columns=["Applicant", "Applicant Quality", "Average Evaluation", "Original Scores", "Rank of Original Scores"])
# Random tie-breaking function
def random_tie_breaking(df, column):
# Shuffle rows with the same value in the specified column
ranks = df[column].rank(method='first', ascending=False)
ties = df.duplicated(column, keep=False)
shuffled_ranks = ranks[ties].sample(frac=1).sort_index()
ranks[ties] = shuffled_ranks
return ranks
# Apply random tie-breaking to rankings
df['Rank of Evaluation'] = random_tie_breaking(df, 'Average Evaluation').astype(int)
df['Rank of Applicant Quality'] = random_tie_breaking(df, 'Applicant Quality').astype(int)
return df
# # Example usage with specified alpha and beta values
# df_results = simulate_applicant_judging(num_applicants=100, num_judges=10, ratings_per_applicant=5, alpha=4, beta=4, judgment_coarse_graining=10)
# df_results.head(30) # Displaying the top 30 rows for brevity
# df_results.sort_values(by='Rank of Evaluation').head(30)
import pandas as pd
def summarize_simulation_runs(num_runs, num_applicants, num_judges, ratings_per_applicant, top_n, alpha, beta,
judge_error=1, judgment_coarse_graining=False):
"""
Runs the applicant judging simulation multiple times and summarizes how often each candidate by quality was in the top n.
:param num_runs: Number of times to run the simulation.
:param num_applicants: Number of applicants to be judged.
:param num_judges: Number of judges available for judging.
:param ratings_per_applicant: Number of ratings each applicant should receive.
:param top_n: Number of top positions to consider in the summary.
:param applicant_std_dev: Standard deviation for the quality of applicants.
:param judge_error_std_dev: Standard deviation for the judge's margin of error.
:param judgment_coarse_graining: Number of buckets for coarse graining or False to disable.
:return: A Pandas DataFrame summarizing the results.
"""
# Initialize counts for each quality-ranked candidate in top n positions
top_n_counts = pd.DataFrame(0, index=range(1, num_applicants + 1), columns=[f'Top {i}' for i in range(1, top_n + 1)])
for _ in range(num_runs):
df_results = simulate_applicant_judging(num_applicants, num_judges, ratings_per_applicant,
alpha=alpha, beta=beta, judge_error=judge_error, judgment_coarse_graining=judgment_coarse_graining)
# Sort by Rank of Applicant Quality
sorted_by_quality = df_results.sort_values(by='Applicant Quality', ascending=False).reset_index()
# Sort by Rank of Evaluation
sorted_by_evaluation = df_results.sort_values(by='Rank of Evaluation').reset_index()
for i in range(top_n):
# Find which quality-ranked candidate is in this top evaluation position
quality_rank = sorted_by_quality[sorted_by_evaluation.loc[i, 'Applicant'] == sorted_by_quality['Applicant']].index[0] + 1
top_n_counts.loc[quality_rank, f'Top {i+1}'] += 1
return top_n_counts
# Example usage
# num_runs = 1000 # Number of simulation runs
# top_n_results = summarize_simulation_runs(num_runs=num_runs, num_applicants=100, num_judges=5, ratings_per_applicant=3,
# top_n=5,alpha=2, beta=1,judge_error=4, judgment_coarse_graining=False)
# top_n_results
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
def plot_top_n_results(top_n_results, num_runs):
"""
Plots a stacked bar chart of the top-n results, filling out what's missing to num_runs in grey.
:param top_n_results: DataFrame containing the counts of each quality-ranked candidate in top n positions.
:param num_runs: Total number of simulation runs.
"""
base_cmap = cmaps.green_green1_r.cut(0.10, 'right').discrete(top_n_results.shape[1])
newcolors = base_cmap(np.linspace(0, 1, top_n_results.shape[1]))
# Define a new color (pink) and add it to the colormap
pink = np.array([178/256, 171/256, 165/256, 1])
newcolors = np.vstack([newcolors, pink])
# Create a new ListedColormap
newcmp = ListedColormap(newcolors)
# Calculate the missing counts to fill up to num_runs
missing_counts = num_runs - top_n_results.sum(axis=1)
# Prepare data for stacked bar chart
data_to_plot = top_n_results.copy()
data_to_plot['Missing'] = missing_counts
# Create a figure and axis for plotting
fig, ax = plt.subplots(figsize=(12, 8))
# Plot stacked bar chart
data_to_plot.head(top_n_results.shape[1]).plot(kind='bar', stacked=True, colormap=newcmp, alpha=.9, ax=ax)
# Plot settings
ax.set_title('How often did the actually best get chosen?', loc='right') # Right-align title
ax.set_xlabel('Real Applicant Rank')
ax.set_ylabel('Selected in this many simulation runs')
# Conditionally add legend
if top_n_results.shape[1] <= 5:
labels = [label.replace("Top", "Rank") for label in top_n_results.columns] + ['Not chosen']
ax.legend(labels=labels, title='Rank in Evaluation', loc='lower center', bbox_to_anchor=(0.5, -0.2), ncol=top_n_results.shape[1]+1) # Legend below the chart
plt.tight_layout()
return fig
# plt.show()
# plot = plot_top_n_results(top_n_results, num_runs)
# num_applicants=100
# num_judges=10
# ratings_per_applicant=5
# top_n=5
# applicant_std_dev=20
# judge_error_std_dev=1
# judgment_coarse_graining=6
# top_n_counts = pd.DataFrame(0, index=range(1, num_applicants + 1), columns=[f'Top {i}' for i in range(1, top_n + 1)])
# df_results = simulate_applicant_judging(num_applicants, num_judges, ratings_per_applicant,
# applicant_std_dev, judge_error_std_dev, judgment_coarse_graining)
# display(df_results.sort_values(by='Rank of Evaluation').head(10))
# # Sort by Rank of Applicant Quality
# sorted_by_quality = df_results.sort_values(by='Applicant Quality', ascending=False).reset_index()
# # Sort by Rank of Evaluation
# sorted_by_evaluation = df_results.sort_values(by='Rank of Evaluation').reset_index()
# for i in range(top_n):
# # Find which quality-ranked candidate is in this top evaluation position
# quality_rank = sorted_by_quality[sorted_by_evaluation.loc[i, 'Applicant'] == sorted_by_quality['Applicant']].index[0] + 1
# top_n_counts.loc[quality_rank, f'Top {i+1}'] += 1
# display(top_n_counts.head(15))
# import numpy as np
# import matplotlib.pyplot as plt
# import seaborn as sns
# from scipy.stats import gaussian_kde
# def visualize_applicant_and_judge_distributions(alpha, beta, judge_error=1):
# """
# Visualizes the distribution of applicants' qualities and an example of a judge's evaluations for a random applicant
# using density estimates with normalized heights.
# :param applicant_std_dev: Standard deviation for the quality of applicants.
# :param judge_error_std_dev: Standard deviation for the judge's margin of error.
# """
# # Generate applicant qualities
# applicant_qualities = np.random.beta(alpha, beta, 5000) * 100
# # Choose a random applicant for judge's evaluation
# random_applicant_quality = np.random.choice(applicant_qualities)
# judge_evaluations = np.random.normal(random_applicant_quality, judge_error, 5000)
# # Calculate KDEs and find peak values
# kde_applicant = gaussian_kde(applicant_qualities)
# kde_judge = gaussian_kde(judge_evaluations)
# x = np.linspace(0, 100, 1000)
# kde_applicant_vals = kde_applicant(x)
# kde_judge_vals = kde_judge(x)
# peak_applicant = np.max(kde_applicant_vals)
# peak_judge = np.max(kde_judge_vals)
# scale_factor = peak_applicant / peak_judge
# # Plotting
# plt.figure(figsize=(12, 6))
# # Plot for distribution of all applicants
# sns.lineplot(x=x, y=kde_applicant_vals, color="blue", label='Applicant Qualities')
# plt.fill_between(x, kde_applicant_vals, color="blue", alpha=0.3)
# plt.title('Distribution of Applicant Qualities')
# plt.xlabel('Quality')
# plt.ylabel('Normalized Density')
# plt.legend()
# plt.xlim(0, 100)
# # # Plot for distribution of a single applicant's evaluations
# # sns.lineplot(x=x, y=kde_judge_vals * scale_factor, color='orange', label='Judge Evaluations')
# # plt.fill_between(x, kde_judge_vals * scale_factor, color="orange", alpha=0.3)
# # plt.title('Distribution of a Judge\'s Evaluations for a Chosen Applicant')
# # plt.xlabel('Evaluation Score')
# # plt.ylabel('Normalized Density')
# # plt.legend()
# # plt.xlim(0, 100)
# plt.tight_layout()
# plt.show()
# # Example usage
# visualize_applicant_and_judge_distributions(alpha=2, beta=1, judge_error=5)
import gradio as gr
import matplotlib.pyplot as plt
from io import BytesIO
from scipy.stats import beta
# Function to plot beta distribution
def plot_beta_distribution(a, b, judgement_variability):
x = np.linspace(0, 1, 1000)
y = beta.pdf(x, a, b)
fig, ax = plt.subplots(figsize=(7, 3)) # Figure size
plt.fill_between(np.linspace(0, 100, 1000), y, color="#ee4d5a", alpha=0.8)
# plt.title('Distribution of Applicant Qualities')
plt.xlabel('True Applicants Quality-Distribution')
plt.xlim(0, 100)
ax.set_yticklabels([]) # Remove y-axis labels
# Drawing the line
line_length = 2 * judgement_variability
line_x = [50 - line_length/2, 50 + line_length/2]
plt.plot(line_x, [0, 0], color='black', linewidth=2)
# Labeling the line
plt.text(np.mean(line_x), 0.02, 'Judgement Variability', ha='center', va='bottom', color='black')
return fig
# Your existing function for running simulation and plotting
def run_simulation_and_plot(num_runs, num_applicants, num_judges, ratings_per_applicant, top_n, alpha, beta, judge_error, judgment_coarse_graining,judgment_coarse_graining_true_false):
if judgment_coarse_graining_true_false == False:
judgment_coarse_graining = False
top_n_results = summarize_simulation_runs(num_runs, num_applicants, num_judges, ratings_per_applicant, top_n, alpha, beta, judge_error, judgment_coarse_graining)
return plot_top_n_results(top_n_results, num_runs)
intro_html = """
<h1>On Rankings</h1>
<p>One of the central experiences of being an academic is the experience of being ranked. We are ranked when we apply for graduate school, or maybe already for a master's degree. We are ranked when we're up for faculty positions. We are ranked when we submit abstracts for conferences. And when we publish papers, we do so, of course, in journals that are ranked. The places where we work, the departments, are ranked, of course, as well. But although rankings apparently are catnip to academics, and probably everybody else as well, we do have some agreement. Most people probably share the intuition that there's something weird or iffy about rankings, and the suspicion that maybe often they are not as informative as there are some beings absolutely everywhere who suggest.</p>
"""
comment_distribution_image = """<p>This is the disrtribution from which our applicants will be sampled:</p>"""
# Building the interface
with gr.Blocks(theme=gr.themes.Monochrome()) as demo:
with gr.Column():
gr.HTML(intro_html)
with gr.Row():
with gr.Column():
run_button = gr.Button("Run Simulations!")
# control applicant distribution
with gr.Group():
alpha_slider = gr.Slider(0.1, 5, step=0.1, value=1.4, label="Alpha (β Distribution)")
beta_slider = gr.Slider(0.1, 5, step=0.1,value=2.7, label="Beta (β Distribution)")
# simumlation-settings:
num_applicants = gr.Slider(10, 300, step=10, value=100, label="Number of Applicants")
num_judges = gr.Slider(1, 100, step=1, value=7, label="Number of Judges")
ratings_per_applicant = gr.Slider(1, 5, step=1, value=3, label="Ratings per Applicant", info='how many different ratings each application gets.')
top_n = gr.Slider(1, 40, step=1, value=5, label="Top N")
judge_error = gr.Slider(0, 10, step=1, value=2, label="Judge Error")
judgment_coarse_graining_true_false = gr.Checkbox(value= True, label="Coarse grain judgements.")
judgment_coarse_graining = gr.Slider(0, 30, step=1, value=7, label="Coarse Graining Factor")
num_runs = gr.Slider(10, 1000, step=10,value=100, label="Number of Runs")
# The button to run the simulation
# Sliders for alpha and beta parameters of the beta distribution
with gr.Column():
with gr.Group():
beta_plot = gr.Plot(label="Applicants quality distribution")
gr.HTML(comment_distribution_image)
# Your existing plot output
plot_output = gr.Plot(label="Simulation Results",show_label=True)
#https://discuss.huggingface.co/t/gradio-changing-background-colour-in-all-devices/42519
# Function call on button click
run_button.click(
run_simulation_and_plot,
inputs=[num_runs, num_applicants, num_judges, ratings_per_applicant, top_n, alpha_slider, beta_slider, judge_error, judgment_coarse_graining,judgment_coarse_graining_true_false],
outputs=[plot_output]
)
alpha_slider.change(plot_beta_distribution, inputs=[alpha_slider, beta_slider,judge_error], outputs=[beta_plot])
beta_slider.change(plot_beta_distribution, inputs=[alpha_slider, beta_slider,judge_error], outputs=[beta_plot])
judge_error.change(plot_beta_distribution, inputs=[alpha_slider, beta_slider,judge_error], outputs=[beta_plot])
demo.load(plot_beta_distribution, inputs=[alpha_slider, beta_slider,judge_error], outputs=[beta_plot])
if __name__ == "__main__":
demo.launch(debug=True)