Statistiche gare Bebras italiano 2017

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]:

Distribuzione dei punteggi

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

pd.options.display.max_rows = None
pd.options.display.max_columns = None
In [3]:
CATS = ('kilo', 'mega', 'giga', 'tera', 'peta')
In [4]:
with open('secret.key') as k:
    key = k.readline().strip()

for i, k in enumerate(CATS):
    if not os.path.exists("results-{}.json".format(k)):
        r = urllib.request.urlopen("https://bebras.it/api?key={}&view=exams&test={}".format(key,50+i))
        with open("results-{}.json".format(k), "w") as tw:
            tw.writelines(r.read().decode('utf-8'))
In [5]:
score = []
for k in CATS:
    with open("results-{}.json".format(k), "r") as t:
        j = json.load(t)
        score += j['exams']
In [6]:
scoredf = pd.DataFrame(score)
In [7]:
# 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['anonid'] = scoredf['team_id'].map(lambda x: hashlib.md5(str(x).encode('utf8')).hexdigest())
scoredf['categoria'] = scoredf['category'].str.lower().astype("category", categories=CATS, ordered=True)
In [8]:
valid = scoredf[scoredf['exam_valid_score'] == 1]
valid.to_csv('anonris.csv', columns=['anonid', 'categoria', 'orainizio', 'punteggio', 'time'])
In [9]:
from IPython.display import display, Markdown

txt = '''<table>
<caption>Squadre partecipanti al Bebras 2017/18 con risultati validi, 
cioè ritenuti confrontabili con gli altri perché privi di anomalie tecniche o organizzative</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():
    s = valid[valid['categoria'] == k]['punteggio'].describe()
    top = valid[(valid['categoria'] == k) & (valid['score'] == int(s['max']))]
    bottom = valid[(valid['categoria'] == k) & (valid['score'] == 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'].count())
txt += '</table>'
display(Markdown(txt))
Squadre partecipanti al Bebras 2017/18 con risultati validi, cioè ritenuti confrontabili con gli altri perché privi di anomalie tecniche o organizzative
Categoria squadre min max media std.dev. I quartile mediana III quartile Squadre al minimo Squadre al massimo
kilo335103926.27.52127310.4%5.5%
mega374603823.88.41825300.3%6.9%
giga177503720.78.21521270.6%3.6%
tera189703721.48.81521280.5%3.6%
peta134803722.08.71623290.7%3.5%
Totale12117
In [10]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('ggplot')

histograms = valid['punteggio'].hist(by=valid['categoria'], bins=30, figsize=(10,8))

Percentili per punteggio

In [11]:
for k in valid['categoria'].unique():
    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 (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839
0.40.40.40.70.70.80.91.21.51.82.32.95.05.76.98.712.013.014.618.923.425.827.635.439.943.247.156.160.161.969.575.679.380.387.090.093.894.594.5
Percentili per la categoria mega (che percentuale di squadre si supera con un dato punteggio)
1234567891011121314151617181920212223242526272829303132333435363738
0.30.30.30.61.21.41.53.23.73.96.08.89.210.415.617.718.523.030.431.833.339.646.048.449.956.161.764.766.972.476.679.281.384.189.891.593.193.1
Percentili per la categoria giga (che percentuale di squadre si supera con un dato punteggio)
12345678910111213141516171819202122232425262728293031323334353637
0.60.60.71.31.62.63.64.56.98.911.014.816.720.624.827.732.937.040.645.249.654.958.663.066.970.674.777.181.984.386.588.491.093.995.696.496.4
Percentili per la categoria tera (che percentuale di squadre si supera con un dato punteggio)
12345678910111213141516171819202122232425262728293031323334353637
0.50.60.71.42.33.34.25.87.69.912.314.717.920.824.227.831.635.539.642.645.750.053.556.960.965.168.671.675.378.381.184.388.191.093.194.196.4
Percentili per la categoria peta (che percentuale di squadre si supera con un dato punteggio)
12345678910111213141516171819202122232425262728293031323334353637
0.70.71.01.42.02.64.15.67.09.110.512.715.918.121.624.628.532.134.938.342.146.149.554.057.962.565.869.674.577.480.083.088.991.192.993.996.5

Analisi delle risposte

In [12]:
rr = []
for r in valid.itertuples():
    for q in r.exam_data['questions']:
        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)
In [13]:
quiz = pd.DataFrame(rr)
In [14]:
MAPBEBRAS = dict((x.split('_')[-1], x.split('_')[1]) for x in list(quiz['q_id'].unique()))
In [15]:
MAPNAMES = {
    'Acquapark': 'Parco acquatico',
    'AllHome': 'Tutti a casa',
     'Balls': 'Palline rotolanti',
 'BeaverTournament': 'Il torneo di pallavolo',
 'Beaverhotel': 'Pulizie',
 'Bebragram': 'Bebragram',
 'CamminoDiArabot': 'Il cammino di Ara-bot',
 'Chain': 'Festoni',
 'ColorPaths': 'Sentieri colorati',
 'Crossbreeds': 'Incroci tra animali',
 'DanceOff': 'Balletto interattivo',
 'DigitRecognition': 'Riconoscimento di cifre',
 'Files': 'Cerca i documenti',
 'FindTheGap': 'Attraverso il passaggio',
 'GiveMeASmile': 'Dammi un sorriso',
 'IconImageCompression': 'Compressione di figure',
 'Intrusion': 'Intrusione',
 'Music': 'Ritornelli',
 'Ninja': 'I soprannomi dei ninja',
 'OfficeLights': "Luci nell'ufficio",
 'PaintingWallPaper': 'La tapezzeria',
 'ParkingLot': 'Il parcheggio',
 'Railroad': 'Ferrovia',
 'Robot': 'Robot',
 'Skyscraper': 'Il grattacielo luminoso',
 'ToyStorage': 'Giocattoli in ordine',
 'Worm': 'Un verme affamato',
 'blocks': 'Un mondo di blocchi',
 'pizzeria': 'Pizze e calzoni',
 'toys': 'Regali di Natale'}
In [16]:
quiz = quiz.rename(columns={'q_time': 'time', 'q_score': 'score', 'q_scoreMax': 'score_max', 'q_class': 'cat'})
In [17]:
quiz['nome'] = quiz['q_id'].str.extract('\d+_.+_(.+)', expand=False)
quiz['edizione'] = quiz['q_id'].str.extract('(\d+)_.+_.+', expand=False)
quiz['bebras'] = quiz['q_id'].str.extract('\d+_(.+)_.+', expand=False)
quiz['completo'] = quiz['score'] == quiz['score_max']
quiz['parziale'] = (quiz['score'] > 0) & (quiz['score'] != quiz['score_max'])
quiz['voto'] = quiz['score'] / quiz['score_max'].astype('float64')
quiz['minuti'] = quiz['time'].map(lambda x: float(x)/60. if float(x) >= 0 else pd.np.NaN)

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

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

for j, k in enumerate(valid['categoria'].unique()):
    plt.subplot(5,1, j+1)
    plt.ylim(0,1.2)
    m = vquiz[vquiz['categoria'] == k].groupby('nome', 
                                             sort=False)[['completo','voto', 'parziale', '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) + 0.4, map(bname, m.index.tolist()), rotation=90)
    for i, y in enumerate(m['voto'].tolist()):
        plt.annotate(s='{:.0f}'.format(m['minuti'].iloc[i]), xy=(i+0.3, y+.08))
        plt.annotate(s='{}'.format(m['score_max'].iloc[i]), xy=(i+0.3, .02), color='yellow')
    plt.legend((c[0],p[0]), ('completo','parziale'))
    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()
In [20]:
plt.figure(figsize=(16,20))

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

    c = plt.bar(pd.np.arange(m.index.size), m['voto'], color='green')
    plt.xticks(pd.np.arange(m.index.size) + 0.4, map(bname, m.index.tolist()), rotation=90)
    for i, y in enumerate(m['voto'].tolist()):
        plt.annotate(s='{}'.format(m['score_max'].iloc[i]), xy=(i+0.3, y+.08), color='red')
    
    plt.title('{}: percentuale di punteggio attribuito in media (in rosso il punteggio massimo ottenibile)'.format(k))

plt.tight_layout()

Analisi delle squadre

In [21]:
members = []
for r in valid.itertuples():
    if r.team_composition and 'members' in r.team_composition:
        for m in r.team_composition['members']:
            m['categoria'] = r.category.lower()
            members.append(m)

pupils = pd.DataFrame(members)
pupils['genere'] = pupils['sex'].map(lambda x: x if x != '-' else pd.np.NaN)
pupils['categoria'] = pupils['categoria'].astype("category", categories=CATS, ordered=True)
In [22]:
gender = pupils[(pupils['name'] != '') | pupils['genere'].notnull()].groupby(['categoria', 'genere']).count()
txt = '''<table><caption>Studenti partecipanti al Bebras 2017 con risultati validi 
(i dati dipendono dalla corretta compilazione dei profili delle squadre)</caption>
<thead>
  <tr><th>Categoria</th>
  <th>studenti</th>
  <th>femmine</th>
  <th>maschi</th>
  <th>squadre con dati mancanti</th>
  <th>media component per squadra</th>
  </tr>
<tbody>
'''

totf = 0
totm = 0
for k in pupils['categoria'].unique():
    f = gender.loc[(k,'f')]['class']
    totf += f
    m = gender.loc[(k,'m')]['class']
    totm += m
    s = valid.groupby('categoria').count().loc[k]['login']
    empty = len(valid[(valid['categoria'] == k) &(valid['team_composition'] == False)])
    txt += '<tr><th>{}</th><td>{}</td><td>{} ({:.1f}%)</td><td>{} ({:.1f}%)</td><td>{}</td><td>{:.1f}</td></tr>'.format(
        k, f+m, f, 100*float(f)/float(f+m), m, 100*float(m)/float(f+m), empty, float(m+s) / float(s - empty)
    )
txt += '<tr><th>Totale:</th><td>{}</td><td>{} ({:.1f}%)</td><td>{} ({:.1f}%)</td></tr>'.format(totf+totm, 
                                                                           totf, 100*float(totf)/float(totf+totm), 
                                                                           totm, 100*float(totm)/float(totf+totm))    
txt += '</table>'
display(Markdown(txt))    
Studenti partecipanti al Bebras 2017 con risultati validi (i dati dipendono dalla corretta compilazione dei profili delle squadre)
Categoria studenti femmine maschi squadre con dati mancanti media component per squadra
kilo116545629 (48.3%)6025 (51.7%)02.8
mega128556068 (47.2%)6787 (52.8%)02.8
giga61792857 (46.2%)3322 (53.8%)02.9
tera62301853 (29.7%)4377 (70.3%)03.3
peta41461137 (27.4%)3009 (72.6%)03.2
Totale:4106417544 (42.7%)23520 (57.3%)

I nomi delle squadre più comuni

In [23]:
import re
from collections import Counter

notwanted = re.compile('^0\d+$|^\d\w|^the$|^and$|^classe$|^squadra$|^gruppo$|^team$|^i+$|^iv$|^\w$|^prima$|^seconda$\
|^terza$|^quarta$|^quinta$')

names = scoredf['team_name'].str.strip().str.lower().tolist()
oknames = filter(lambda w: not notwanted.match(w), names)

c = Counter(oknames)

c.most_common(30)
Out[23]:
[('i fantastici 4', 33),
 ('i matematici', 31),
 ('leoni', 23),
 ('gli informatici', 21),
 ('blu', 18),
 ('aquile', 16),
 ('tigri', 16),
 ('gli invincibili', 16),
 ('the best', 15),
 ('marte', 14),
 ('lupi', 13),
 ('ghepardi', 12),
 ('pantere', 12),
 ('i castori', 11),
 ('verdi', 10),
 ('matematici', 10),
 ('i fantastici quattro', 10),
 ('i cervelloni', 10),
 ('nettuno', 10),
 ('i mitici', 10),
 ('venere', 10),
 ('i leoni', 10),
 ('informatici', 10),
 ('squali', 10),
 ('saturno', 10),
 ('i tecnologici', 10),
 ('i campioni', 9),
 ('delfini', 9),
 ('rossi', 9),
 ('giove', 9)]
In [24]:
plt.axis('off')
os = scoredf['operating_system'].value_counts().plot.pie(autopct='%.1f', radius=1.22,
                                                    explode=[.06*i*i for i in range(len(scoredf['operating_system'].unique()))],
                                                    figsize=(5,5), title='Sistemi operativi utilizzati')