from shiny import App, ui, render, reactive, req
import pandas as pd
import matplotlib.pyplot as plt
import io
import numpy as np
# Fonction pour normaliser les scores
def normaliser_scores(x):
return 100 * x / x.max()
app_ui = ui.page_navbar(
ui.nav(
"Analyse",
ui.layout_sidebar(
ui.sidebar(
ui.input_file("fichier", "Importer un fichier CSV", accept=[".csv"]),
ui.input_numeric("poids1", "Poids Indicateur 1 (%)", value=40, min=0, max=100, step=5),
ui.input_numeric("poids2", "Poids Indicateur 2 (%)", value=20, min=0, max=100, step=5),
ui.input_numeric("poids3", "Poids Indicateur 3 (%)", value=20, min=0, max=100, step=5),
ui.input_numeric("poids4", "Poids Indicateur 4 (%)", value=10, min=0, max=100, step=5),
ui.input_numeric("poids5", "Poids Indicateur 5 (%)", value=10, min=0, max=100, step=5),
ui.output_text("somme_poids"),
ui.hr(),
ui.input_numeric("n_afficher", "Nombre de candidats à afficher", value=10, min=1, step=1),
ui.input_action_button("calculer", "Calculer les scores", class_="btn-primary"),
),
ui.navset_tab(
ui.nav(
"Score Final",
ui.row(
ui.column(6, ui.output_plot("histogramme")),
ui.column(6, ui.output_table("top_candidats")),
),
ui.output_plot("barplot_scores"),
),
ui.nav(
"Histogrammes par Indicateur",
ui.row(
ui.column(4, ui.output_plot("hist_indicateur1")),
ui.column(4, ui.output_plot("hist_indicateur2")),
ui.column(4, ui.output_plot("hist_indicateur3")),
),
ui.row(
ui.column(4, ui.output_plot("hist_indicateur4")),
ui.column(4, ui.output_plot("hist_indicateur5")),
),
),
ui.nav(
"Classement par Indicateur",
ui.row(
ui.column(6, ui.output_table("classement_indicateur1")),
ui.column(6, ui.output_table("classement_indicateur2")),
),
ui.row(
ui.column(6, ui.output_table("classement_indicateur3")),
ui.column(6, ui.output_table("classement_indicateur4")),
),
ui.output_table("classement_indicateur5"),
),
),
),
),
ui.nav(
"Méthodologie",
ui.page_fluid(
ui.h2("Méthodologie utilisée pour le classement des candidats"),
ui.p("Cette application utilise une méthode de pondération pour calculer un score final pour chaque candidat."),
ui.h4("1. Normalisation des Indicateurs"),
ui.p("Les valeurs des indicateurs sont normalisées sur une échelle de 0 à 100 en appliquant une règle de trois. "
"Cela permet de comparer les indicateurs entre eux, même s’ils ont des échelles initiales différentes."),
ui.h4("2. Pondération des Indicateurs"),
ui.p("Chaque indicateur est multiplié par un poids défini par l'utilisateur. La somme des poids doit être égale à 100% "
"pour garantir que chaque indicateur contribue proportionnellement au score final."),
ui.h4("3. Calcul du Score Final"),
ui.p("Le score final pour chaque candidat est la somme des scores normalisés de chaque indicateur multipliée par leur poids respectif. "
"Les candidats sont ensuite classés en fonction de leur score final."),
ui.h4("Exemple de Calcul"),
ui.p("Supposons qu'un candidat ait les valeurs d'indicateurs suivantes :"),
ui.tags.ul(
ui.tags.li("Indicateur 1 : 80"),
ui.tags.li("Indicateur 2 : 70"),
ui.tags.li("Indicateur 3 : 60"),
ui.tags.li("Indicateur 4 : 50"),
ui.tags.li("Indicateur 5 : 90")
),
ui.p("Supposons également que les valeurs maximales observées pour chaque indicateur soient respectivement de 100, 80, 75, 90, et 100. Pour les poids, "
"nous utiliserons les valeurs suivantes :"),
ui.tags.ul(
ui.tags.li("Poids Indicateur 1 : 40%"),
ui.tags.li("Poids Indicateur 2 : 20%"),
ui.tags.li("Poids Indicateur 3 : 20%"),
ui.tags.li("Poids Indicateur 4 : 10%"),
ui.tags.li("Poids Indicateur 5 : 10%")
),
ui.h4("Étape 1 : Normalisation des Indicateurs"),
ui.p("Pour chaque indicateur, nous normalisons la valeur du candidat en utilisant la formule : "),
ui.tags.code("Score Normalisé = (Valeur du Candidat / Valeur Max) * 100"),
ui.p("Les scores normalisés pour chaque indicateur seraient alors :"),
ui.tags.ul(
ui.tags.li("Indicateur 1 : (80 / 100) * 100 = 80"),
ui.tags.li("Indicateur 2 : (70 / 80) * 100 = 87.5"),
ui.tags.li("Indicateur 3 : (60 / 75) * 100 = 80"),
ui.tags.li("Indicateur 4 : (50 / 90) * 100 ≈ 55.6"),
ui.tags.li("Indicateur 5 : (90 / 100) * 100 = 90")
),
ui.h4("Étape 2 : Calcul du Score Final avec les Poids"),
ui.p("Le score final est calculé en appliquant les poids à chaque indicateur normalisé, comme suit :"),
ui.tags.ul(
ui.tags.li("Score Final = (80 * 0.4) + (87.5 * 0.2) + (80 * 0.2) + (55.6 * 0.1) + (90 * 0.1)"),
ui.tags.li("Score Final ≈ 32 + 17.5 + 16 + 5.56 + 9 = 80.06")
),
ui.p("Le score final pour ce candidat serait donc de 80.06.")
)
),
title="Concours Interne ANPE 2024",
id="navbar",
)
def server(input, output, session):
@reactive.calc
def donnees():
req(input.fichier())
df = pd.read_csv(input.fichier()[0]["datapath"], sep=";")
return df
@output
@render.text
def somme_poids():
somme = input.poids1() + input.poids2() + input.poids3() + input.poids4() + input.poids5()
if somme == 100:
return "✓ La somme des poids est égale à 100%"
else:
return f"⚠ La somme des poids est {somme}%. Elle doit être égale à 100%"
@reactive.calc
@reactive.event(input.calculer)
def scores_calcules():
df = donnees()
req(not df.empty)
somme_poids = input.poids1() + input.poids2() + input.poids3() + input.poids4() + input.poids5()
req(somme_poids == 100)
df = df.copy()
for i in range(1, 6):
df[f'Indicateur{i}'] = normaliser_scores(df[f'Indicateur{i}'])
df['Score_Final'] = (
df['Indicateur1'] * (input.poids1()/100) +
df['Indicateur2'] * (input.poids2()/100) +
df['Indicateur3'] * (input.poids3()/100) +
df['Indicateur4'] * (input.poids4()/100) +
df['Indicateur5'] * (input.poids5()/100)
)
return df.sort_values('Score_Final', ascending=False)
@output
@render.plot
def histogramme():
df = scores_calcules()
req(not df.empty)
plt.figure(figsize=(8, 6))
plt.hist(df['Score_Final'], bins=20, color='#2C3E50', edgecolor='white')
plt.title('Distribution des Scores Finaux')
plt.xlabel('Score Final')
plt.ylabel('Nombre de Candidats')
@output
@render.table
def top_candidats():
df = scores_calcules()
req(not df.empty)
df = df.head(input.n_afficher())
return (df[['Candidat', 'Score_Final']]
.reset_index(drop=True)
.assign(Rang=lambda x: x.index + 1)
.round(2)[['Rang', 'Candidat', 'Score_Final']])
@output
@render.plot
def barplot_scores():
df = scores_calcules()
req(not df.empty)
data = df.head(input.n_afficher())
plt.figure(figsize=(10, 6))
plt.bar(range(len(data)), data['Score_Final'], color='#3498db')
plt.xticks(range(len(data)), data['Candidat'], rotation=45)
plt.title(f"Scores des {input.n_afficher()} premiers candidats")
plt.xlabel('Candidat')
plt.ylabel('Score Final')
plt.tight_layout()
# Histogrammes pour chaque indicateur (définis individuellement)
@output
@render.plot
def hist_indicateur1():
df = scores_calcules()
req(not df.empty)
plt.figure(figsize=(6, 4))
plt.hist(df['Indicateur1'], bins=20, color='#3498db', edgecolor='white')
plt.title('Distribution de Indicateur 1')
plt.xlabel('Indicateur 1')
plt.ylabel('Nombre de Candidats')
@output
@render.plot
def hist_indicateur2():
df = scores_calcules()
req(not df.empty)
plt.figure(figsize=(6, 4))
plt.hist(df['Indicateur2'], bins=20, color='#e74c3c', edgecolor='white')
plt.title('Distribution de Indicateur 2')
plt.xlabel('Indicateur 2')
plt.ylabel('Nombre de Candidats')
@output
@render.plot
def hist_indicateur3():
df = scores_calcules()
req(not df.empty)
plt.figure(figsize=(6, 4))
plt.hist(df['Indicateur3'], bins=20, color='#2ecc71', edgecolor='white')
plt.title('Distribution de Indicateur 3')
plt.xlabel('Indicateur 3')
plt.ylabel('Nombre de Candidats')
@output
@render.plot
def hist_indicateur4():
df = scores_calcules()
req(not df.empty)
plt.figure(figsize=(6, 4))
plt.hist(df['Indicateur4'], bins=20, color='#f1c40f', edgecolor='white')
plt.title('Distribution de Indicateur 4')
plt.xlabel('Indicateur 4')
plt.ylabel('Nombre de Candidats')
@output
@render.plot
def hist_indicateur5():
df = scores_calcules()
req(not df.empty)
plt.figure(figsize=(6, 4))
plt.hist(df['Indicateur5'], bins=20, color='#9b59b6', edgecolor='white')
plt.title('Distribution de Indicateur 5')
plt.xlabel('Indicateur 5')
plt.ylabel('Nombre de Candidats')
# Classements pour chaque indicateur (définis individuellement)
@output
@render.table
def classement_indicateur1():
df = scores_calcules()
req(not df.empty)
return (df[['Candidat', 'Indicateur1']]
.sort_values('Indicateur1', ascending=False)
.head(input.n_afficher())
.reset_index(drop=True)
.assign(Rang=lambda x: x.index + 1)
.round(2)[['Rang', 'Candidat', 'Indicateur1']])
@output
@render.table
def classement_indicateur2():
df = scores_calcules()
req(not df.empty)
return (df[['Candidat', 'Indicateur2']]
.sort_values('Indicateur2', ascending=False)
.head(input.n_afficher())
.reset_index(drop=True)
.assign(Rang=lambda x: x.index + 1)
.round(2)[['Rang', 'Candidat', 'Indicateur2']])
# [Tout le code précédent reste identique jusqu'à classement_indicateur2]
@output
@render.table
def classement_indicateur3():
df = scores_calcules()
req(not df.empty)
return (df[['Candidat', 'Indicateur3']]
.sort_values('Indicateur3', ascending=False)
.head(input.n_afficher())
.reset_index(drop=True)
.assign(Rang=lambda x: x.index + 1)
.round(2)[['Rang', 'Candidat', 'Indicateur3']])
@output
@render.table
def classement_indicateur4():
df = scores_calcules()
req(not df.empty)
return (df[['Candidat', 'Indicateur4']]
.sort_values('Indicateur4', ascending=False)
.head(input.n_afficher())
.reset_index(drop=True)
.assign(Rang=lambda x: x.index + 1)
.round(2)[['Rang', 'Candidat', 'Indicateur4']])
@output
@render.table
def classement_indicateur5():
df = scores_calcules()
req(not df.empty)
return (df[['Candidat', 'Indicateur5']]
.sort_values('Indicateur5', ascending=False)
.head(input.n_afficher())
.reset_index(drop=True)
.assign(Rang=lambda x: x.index + 1)
.round(2)[['Rang', 'Candidat', 'Indicateur5']])
app = App(app_ui, server)