Statistiche gare Bebras italiano 2021

In [1]:
from IPython.display import HTML

HTML('''<script>
code_show=true; 
function code_toggle() {
 if (code_show){
 $('div.input').hide();
 } else {
 $('div.input').show();
 }
 code_show = !code_show
} 
$( document ).ready(code_toggle);
</script>
<input type="button" value="Clicca per vedere/nascondere il codice Python" onclick="code_toggle()">''')
Out[1]:
In [2]:
import warnings
#warnings.filterwarnings('once')
warnings.filterwarnings('ignore')

Distribuzione dei punteggi

In [3]:
import pandas as pd
import json, hashlib, urllib, os.path

pd.options.display.max_rows = None
pd.options.display.max_columns = None
In [4]:
CATS = ('kilo','mega','giga','tera','peta')
SUBS = ('single', 'double')
CATEGORIES = tuple(f'{c}-{s}' for c in CATS for s in SUBS)
CAT_FILES = tuple(f'{c}-{s}' for s in SUBS for c in CATS)
In [5]:
with open('secret.key') as k:
    key = k.readline().strip()

for i, k in enumerate(CAT_FILES):
    if not os.path.exists(f"results-{k}.json"):
        r = urllib.request.urlopen(f"https://bebras.it/api?key={key}&view=exams&edition=bebras_2021&events=0&test={98+i}")
        with open(f"results-{k}.json", "w") as tw:
            tw.writelines(r.read().decode('utf-8'))
In [6]:
score = []
for k in CATEGORIES:
    with open(f"results-{k}.json", "r") as t:
        j = json.load(t)
        score += j['exams']
In [7]:
scoredf = pd.DataFrame(score)
In [8]:
# L'orario va corretto per il fuso orario

scoredf['server_start'] = pd.to_datetime(scoredf['exam_date'].astype('int64') + 60*60, unit='s')
scoredf['orainizio'] = pd.np.floor((scoredf['exam_date'].astype('int64') + 60*60) / (45*60)) # ore da 45', il tempo di gara
scoredf['punteggio'] = pd.to_numeric(scoredf['score'])
scoredf['punteggio_norm'] = scoredf['punteggio'].map(lambda x: x if x >= 0 else 0)
scoredf['anonid'] = scoredf['team_id'].map(lambda x: hashlib.md5(str(x).encode('utf8')).hexdigest())
scoredf['categoria'] = scoredf['category'].str.lower().astype(pd.api.types.CategoricalDtype(categories = CATEGORIES, ordered=True))
In [9]:
valid = scoredf[scoredf['exam_valid_score'] == 1]
valid.to_csv('anonris.csv', columns=['anonid', 'categoria', 'orainizio', 'punteggio', 'punteggio_norm', 'time'])
In [10]:
from IPython.display import display, Markdown

txt = '''<table>
<caption>Squadre partecipanti al Bebras 2021/22 con risultati correttamente registrati</caption>
<thead>
  <tr><th>Categoria</th>
  <th>squadre</th>
  <th> min </th>
  <th> max </th>
  <th> media </th>
  <th> std.dev. </th>
  <th>I quartile </th>
  <th>mediana </th>
  <th>III quartile</th>
  <th>Squadre al minimo</th>
  <th>Squadre al massimo</th>
</tr>
<tbody>
'''
for k in valid['categoria'].unique().sort_values():
    s = valid[valid['categoria'] == k]['punteggio_norm'].describe()
    top = valid[(valid['categoria'] == k) & (valid['punteggio_norm'] == int(s['max']))]
    bottom = valid[(valid['categoria'] == k) & (valid['punteggio_norm'] == int(s['min']))]
    txt += "<tr><th>{}</th><td>{}</td><td>{}</td><td>{}</td><td>{:.1f}</td>\
<td>{:3.1f}</td><td>{}</td><td>{}</td><td>{}</td><td>{:.1f}%</td><td>{:.1f}%</td></tr>".format(k, 
                                                              int(s['count']),
                                                              int(s['min']),
                                                              int(s['max']),
                                                              float(s['mean']),
                                                              float(s['std']),
                                                              int(s['25%']), 
                                                              int(s['50%']), 
                                                              int(s['75%']),
                                                              100*len(bottom)/float(s['count']),
                                                              100*len(top)/float(s['count']))
txt += '<tfoot><tr><th>Totale</th><td>{}</td></tr>'.format(valid['punteggio_norm'].count())
txt += '</table>'
display(Markdown(txt))
Squadre partecipanti al Bebras 2021/22 con risultati correttamente registrati
Categoria squadre min max media std.dev. I quartile mediana III quartile Squadre al minimo Squadre al massimo
kilo-single163804815.210.3714226.3%0.4%
kilo-double182804818.99.91218252.2%0.5%
mega-single729404812.59.65111810.1%0.2%
mega-double428104814.710.0713216.5%0.2%
giga-single33020469.98.2381512.1%0.0%
giga-double204204811.48.75101711.1%0.1%
tera-single303704814.99.0814214.0%0.0%
tera-double160304514.99.0814214.8%0.1%
peta-single161705020.310.61219281.9%0.1%
peta-double141105021.010.31321281.3%0.1%
Totale28053
In [11]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('ggplot')

histograms = valid['punteggio_norm'].hist(by=valid['categoria'], bins=24, figsize=(10,16), layout=(5, 2))

Percentili per punteggio

In [12]:
for k in valid['categoria'].unique().sort_values():
    tot = float(valid[(valid['categoria'] == k)]['punteggio'].count())
    top = int(valid[(valid['categoria'] == k)]['punteggio'].max())
    pp = [100 * valid[(valid['categoria'] == k) & (valid['punteggio'] < i)]['punteggio'].count()/tot for i in range(1,top+1)]
    txt = '''<table>
    <caption>Percentili per la categoria {} (che percentuale di squadre si supera con un dato punteggio)</caption>
    <thead>'''.format(k)
    txt += ''.join(['<td>{}</td>'.format(i) for i in range(1,top+1)])
    txt += '<tbody>'
    txt += ''.join(['<td>{:.1f}</td>'.format(f) for f in pp])
    txt += '</table>'
    display(Markdown(txt))    
Percentili per la categoria kilo-single (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
6.38.510.613.216.519.222.725.629.932.436.039.943.547.850.654.657.962.065.268.971.674.177.480.182.783.885.586.388.289.590.792.293.093.894.996.497.097.597.998.398.499.099.299.399.599.599.699.6
Percentili per la categoria kilo-double (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
2.23.24.35.26.77.910.812.314.817.319.923.627.431.233.838.542.747.551.355.959.263.567.069.973.975.678.780.782.985.387.288.890.091.192.593.994.695.496.096.697.198.699.099.199.399.499.599.5
Percentili per la categoria mega-single (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
10.113.116.719.823.026.931.135.640.344.648.652.356.059.863.866.969.973.276.078.380.782.884.786.287.989.590.591.792.893.894.795.496.096.697.197.698.198.598.798.898.999.199.699.799.899.899.899.8
Percentili per la categoria mega-double (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
6.58.510.913.416.619.723.126.931.135.338.842.846.550.053.657.761.064.266.970.073.476.078.580.582.584.686.388.089.390.692.193.494.395.195.796.797.398.098.398.498.798.899.699.799.899.899.899.8
Percentili per la categoria giga-single (che percentuale di squadre si supera con un dato punteggio)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
12.116.721.826.330.336.141.046.251.655.860.364.468.071.474.477.279.982.384.286.187.989.690.892.093.294.695.796.697.397.898.598.599.099.299.399.599.799.799.899.899.899.999.999.999.9100.0
Percentili per la categoria giga-double (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
11.114.318.221.424.429.233.638.142.046.351.356.260.264.068.070.874.177.279.582.084.486.388.090.091.292.293.694.895.496.397.197.698.698.798.999.399.599.699.899.899.899.899.999.999.999.999.999.9
Percentili per la categoria tera-single (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
4.05.26.58.512.614.119.323.226.930.634.739.244.448.252.256.460.364.167.670.773.977.079.582.284.686.688.790.591.792.994.295.296.296.897.898.298.698.899.299.499.799.899.999.999.999.9100.0100.0
Percentili per la categoria tera-double (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445
4.86.27.89.513.315.019.722.125.529.133.437.943.347.351.256.160.964.868.170.974.176.779.782.384.987.088.990.692.193.494.695.195.997.097.998.498.798.899.199.399.599.599.899.999.9
Percentili per la categoria peta-single (che percentuale di squadre si supera con un dato punteggio)
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
1.92.33.54.15.37.28.510.913.916.818.922.826.128.331.536.238.942.146.350.554.158.060.463.266.869.972.475.078.680.482.784.585.787.388.990.792.093.294.294.796.096.797.698.498.698.799.099.399.899.9
Percentili per la categoria peta-double (che percentuale di squadre si supera con un dato punteggio)
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
1.31.72.32.73.95.26.99.010.813.616.919.723.226.329.933.536.541.444.446.849.954.157.661.264.668.170.573.476.378.781.583.385.186.388.790.491.892.894.094.795.896.797.798.499.099.299.699.699.899.9

Analisi delle risposte

In [13]:
rr = []
errors = 0
for r in valid.itertuples():
    for q in r.exam_data['questions']:
        try:
            t = dict((k, q[k]) for k in ('q_id','q_class','q_score','q_scoreMax','q_time'))
            t['anonid'] = r.anonid
            rr.append(t)
        except Exception as e:
            #print(q, e)
            errors += 1
print(errors)
6043
In [14]:
quiz = pd.DataFrame(rr)
In [15]:
MAPBEBRAS = dict((x.split('_')[-1], x.split('_')[1]) for x in list(quiz['q_id'].unique()))
In [16]:
MAPNAMES = {
    'Q01': 'Lettere',
    'Q02': 'Vestito da ballo',
    'Q03': 'Assistente virtuale',
    'Q04': 'Timbri',
    'Q05': 'Mappa concettuale',
    'Q06': 'Sequenze di DNA',
    'Q07': 'Collane',
    'Q08': 'Display difettoso',
    'Q09': 'Fotografie del gatto',
    'Q10': 'Case nel villaggio',
    'Q11': 'Auto con guida autonoma',
    'Q12': 'Un gioco coi birilli',
    'Q13': "Ricostruire il DNA",
    'Q14': "Cuculi e nidi",
    'Q15': 'Topo-robot',
    'Q16': 'Canguri',
    'Q17': 'I gruppi di lavoro',
    'Q18': 'La pila di frutta',
    'Q19': 'Logistica museale',
    'Q20': 'Cenni del capo',
    'Q21': 'Disegni programmati',
    'Q22': 'Un codice compresso',
    'Q23': 'Rilevamento di guasti',
    'Q24': 'Andiamo in biblioteca',
    'Q25': "Di corsa all'incontro",
    'Q26': 'Salviamo gli alberi',
    'Q27': 'Piastrelle Truchet',
    'Q28': "Ada l'ingegnera",
}
In [17]:
quiz = quiz.rename(columns={'q_time': 'time', 'q_score': 'score', 'q_scoreMax': 'score_max', 'q_class': 'cat'})
In [18]:
quiz['nome'] = quiz['q_id'].str.extract('[0-9]+_(.+)', expand=False)
quiz['edizione'] = quiz['q_id'].str.extract('([0-9]+)_.+', expand=False)
quiz['completo'] = quiz['score'] == quiz['score_max']
quiz['parziale'] = (quiz['score'] > 0) & (quiz['score'] != quiz['score_max'])
quiz['penalizzato'] = quiz['score'] < 0
quiz['voto'] = quiz['score'] / quiz['score_max'].astype('float64')
quiz['minuti'] = quiz['time'].map(lambda x: float(x)/60. if float(x) >= 0 and float(x) <= 45*60 else pd.np.NaN)

#quiz.to_csv('quiz.csv', columns=['anonid', 'cat', 'edizione', 'nome', 'bebras', 'score', 'score_max', 'time'])
In [19]:
vquiz = pd.merge(valid[['anonid', 'categoria', 'punteggio','punteggio_norm','orainizio','teacher_id','school_cap']], quiz, on='anonid')
In [20]:
plt.figure(figsize=(16,40))

def bname(n):
    if n in MAPBEBRAS and n in MAPNAMES:
        return '{}'.format(MAPNAMES[n])
    else:
        return n

for j, k in enumerate(valid['categoria'].unique().sort_values()):
    plt.subplot(len(valid['categoria'].unique()), 1, j+1)
    plt.ylim(0,1.2)
    m = vquiz[vquiz['categoria'] == k].groupby('nome', 
                                             sort=False)[['completo','voto', 'parziale', 'penalizzato', 'minuti','score_max']].mean()
    m['vparziale'] = m['voto'] - m['completo']

    c = plt.bar(pd.np.arange(m.index.size), m['completo'], color='blue')
    p = plt.bar(pd.np.arange(m.index.size), m['parziale'], bottom=m['completo'], color='lightblue')   
    plt.xticks(pd.np.arange(m.index.size), map(bname, m.index.tolist()), rotation=45)
    plt.ylim([0,1])
    plt.yticks(pd.np.arange(0,1.2,.2), ['{:.0f}%'.format(100*y) for y in pd.np.arange(0,1.2,.2)])
    for i, y in enumerate(m['voto'].tolist()):
        plt.annotate(text='{:.0f}\''.format(m['minuti'].iloc[i]), xy=(i, .75*m['completo'].iloc[i]), color='white')
        plt.annotate(text='{:.0f}'.format(m['score_max'].iloc[i]), xy=(i-.15, .02), color='yellow', fontsize='x-large')
    plt.legend((c[0],p[0]), ('completo','parziale'), loc=(.92,.6))
    plt.title('{}: tassi di soluzione (il numero in alto indica i minuti spesi in media sul quesito, \
il numero in basso il punteggio massimo ottenibile)'.format(k))

plt.tight_layout()
plt.savefig('tassisol.png')