Statistiche gare Bebras italiano 2016

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
In [3]:
score = None
with open("highscore.json") as hs:
    score = json.load(hs)
In [4]:
scoredf = pd.DataFrame(score['exams'])

# 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'].str.cat(scoredf['exam_date']).map(lambda x: hashlib.md5(x).hexdigest())
scoredf['categoria'] = scoredf['category'].astype("category", categories=["Kilo","Mega","Giga","Tera","Peta"], ordered=True)
In [5]:
valid = scoredf[scoredf['exam_valid_score'] == 1]
valid.to_csv('anonris.csv', columns=['anonid', 'categoria', 'orainizio', 'punteggio', 'time'])
In [6]:
from IPython.display import display, Markdown

txt = '''<table>
<caption>Squadre partecipanti al Bebras 20156 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>
  </tr>
<tbody>
'''
for k in valid['categoria'].unique():
    s = valid[valid['categoria'] == k]['punteggio'].describe()
    txt += "<tr><th>{}</th><td>{}</td><td>{}</td><td>{}</td><td>{:.1f}</td>\
<td>{:3.1f}</td><td>{}</td><td>{}</td><td>{}</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%']))
txt += '<tfoot><tr><th>Totale</th><td>{}</td></tr>'.format(valid['punteggio'].count())
txt += '</table>'
display(Markdown(txt))
Squadre partecipanti al Bebras 20156 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
Kilo265905819.810.1121926
Mega216505320.79.9132027
Giga104805018.48.5121824
Tera122704518.58.0131824
Peta106405421.89.9152128
Totale8163
In [7]:
%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 [8]:
for k in valid['categoria'].unique():
    tot = float(valid[(valid['categoria'] == k)]['punteggio'].count())
    pp = [100 * valid[(valid['categoria'] == k) & (valid['punteggio'] < i)]['punteggio'].count()/tot for i in xrange(1,61)]
    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 xrange(1,61)])
    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)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
0.71.01.72.53.65.27.510.012.414.717.621.925.829.733.737.641.745.749.653.156.960.764.067.470.773.376.078.981.583.385.186.888.289.791.292.193.093.995.095.696.497.197.998.198.498.899.299.299.499.499.599.599.699.799.799.899.899.8100.0100.0
Percentili per la categoria Mega (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
0.10.40.91.82.73.75.27.39.512.014.618.222.426.129.733.937.641.044.948.452.156.059.863.166.870.374.277.279.281.883.985.587.389.190.491.592.993.994.795.495.896.497.197.598.298.799.099.299.499.799.799.9100.0100.0100.0100.0100.0100.0100.0100.0
Percentili per la categoria Giga (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
0.50.81.32.43.75.46.99.011.415.818.422.726.631.436.940.944.749.253.757.762.966.169.673.176.178.481.184.486.688.890.992.494.095.796.697.497.998.698.799.099.399.499.599.599.899.999.999.999.999.9100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0
Percentili per la categoria Tera (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
0.60.81.52.03.34.35.97.610.913.016.418.723.627.332.536.342.847.652.957.061.565.670.172.678.180.884.487.290.291.493.294.395.796.296.997.798.098.198.898.999.299.499.499.699.9100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0100.0
Percentili per la categoria Peta (che percentuale di squadre si supera con un dato punteggio)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
0.40.81.32.12.83.74.96.08.110.612.514.717.920.223.428.031.735.140.745.147.951.655.959.663.066.669.472.475.578.681.083.786.187.689.390.992.393.494.595.395.996.697.297.597.898.598.999.199.299.399.599.699.799.8100.0100.0100.0100.0100.0100.0

Analisi delle risposte

In [9]:
rr = []
for r in valid.itertuples():
    for q in r.questions:
        t = q.copy()
        t['anonid'] = r.anonid
        rr.append(t)
In [10]:
quiz = pd.DataFrame(rr)

MAPBEBRAS = {
    'Pennello': 'PK-03',
    'RicettaSegreta': 'HU-02',
    'MessaggiSegreti': 'UK-06',
    'Coccinelle': 'SK-10',
    'ColoraFiori': 'SK-04',
    'ConiBiglietti': 'FR-02',
    'PallaCastoro': 'JP-03',
    'Commissioni': 'LT-03',
    'Tappi': 'JP-06',
    'Soccer': 'US-07b',
    'Direzioni': 'IE-05',
    'Robot': 'FR-04',
    'Scanner': 'MY-02',
    'SacchiAscensore': 'CZ-02a',
    'Rafting': 'LT-02',
    'Insalata': 'DE-08',
    'Cannone': 'IT-06',
    'Mug': 'TW-05',
    'HealthCare': 'CH-03', 
    'Thief': 'BE-02',
    'FiltroMediano': 'RU-02',
    'Bolle': 'IT-03',
    'Tunnel': 'CH-04a',
    'Isole': 'FR-03',
    'Colori': 'UK-04',
    'Mapreduce': 'CA-08b', 
    'BandiereCompresse': 'CZ-04',
    'Smartphone': 'IT-04',
    'Albero': 'CA-05',
    'Pozioni': 'JP-01',
    'Legno': 'CA-01',
    'Avventura': 'FR-07',
    'Kix': 'NL-04', 
    'Biglie': 'IT-02b', 
    'Forme': 'CA-09'
}


MAPNAMES = {
    'Pennello': "Il rullo dell'imbianchino",
    'RicettaSegreta': 'La ricetta segreta',
    'MessaggiSegreti': 'Messaggi segreti',
    'Coccinelle': 'Coccinelle',
    'ColoraFiori': 'Colora i fiori',
    'ConiBiglietti': 'Coni e biglietti',
    'PallaCastoro': 'BeaverBall',
    'Commissioni': 'Commissioni',
    'Tappi': 'Tappi',
    'Soccer': 'Partita di calcio',
    'Direzioni': 'Direzioni concorrenti',
    'Robot': 'Fai uscire il robot',
    'Scanner': 'Scanner per immagini',
    'SacchiAscensore': "Sacchi nell'ascensore",
    'Rafting': 'Rafting',
    'Insalata': 'Una tartaruga sistematica',
    'Cannone': 'Artiglieria programmabile',
    'Mug': 'Una collezione di tazze',
    'HealthCare': 'Pronto soccorso', 
    'Thief': 'Caccia al ladro',
    'FiltroMediano': 'Filtro mediano',
    'Bolle': 'Bolle',
    'Tunnel': 'La galleria',
    'Isole': 'Isole',
    'Colori': 'Quanti colori?',
    'Mapreduce': 'Fra parentesi', 
    'BandiereCompresse': 'Bandiere',
    'Smartphone': 'In fila per tre',
    'Albero': "L'albero di Natale",
    'Pozioni': 'Pozioni magiche',
    'Legno': 'Il legno buono',
    'Avventura': 'Avventura',
    'Kix': 'Codice a barre', 
    'Biglie': 'Biglie', 
    'Forme': 'Gioco di forme'
}

quiz['nome'] = quiz['code'].str.extract('\d+_.+_(.+)', expand=False)
quiz['cat'] = quiz['code'].str.extract('\d+_(.+)_.+', expand=False)
quiz['edizione'] = quiz['code'].str.extract('(\d+)_.+_.+', expand=False)
quiz['bebras'] = quiz['nome'].map(lambda x: MAPBEBRAS[x])
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: x/(1000*60) if x >= 0 else pd.np.NaN)

quiz.to_csv('quiz.csv', columns=['anonid', 'cat', 'edizione', 'nome', 'bebras', 'score', 'score_max', 'time'])
In [11]:
vquiz = pd.merge(valid[['anonid', 'categoria', 'punteggio','orainizio','teacher_id','school_cap']], quiz, on='anonid')
In [12]:
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)
    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'])
    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='red')
    plt.legend((c[0],p[0]), ('completo','parziale'))
    plt.title('{}: tassi di soluzione (il numero in nero indica i minuti spesi in media sul quesito, \
il numero in rosso il punteggio massimo ottenibile)'.format(k))

plt.tight_layout()
In [13]:
plt.figure(figsize=(16,20))

for j, k in enumerate(valid['categoria'].unique()):
    plt.subplot(5,1, j+1)
    plt.ylim(0,1)
    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 [14]:
members = []
for r in valid[valid['team_composition'] != False].itertuples():
    k = r.categoria
    for m in r.team_composition['members']:
        m['categoria'] = k
        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=["Kilo","Mega","Giga","Tera","Peta"], ordered=True)
In [15]:
gender = pupils[(pupils['name'] != '') | pupils['genere'].notnull()].groupby(['categoria', 'genere']).count()
txt = '''<table><caption>Studenti partecipanti al Bebras 2016 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['Kilo']['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 2016 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
Kilo93984611 (49.1%)4787 (50.9%)1162.9
Mega75443422 (45.4%)4122 (54.6%)822.6
Giga37151725 (46.4%)1990 (53.6%)111.8
Tera43211101 (25.5%)3220 (74.5%)762.3
Peta3429911 (26.6%)2518 (73.4%)1352.1
Totale:2840711770 (41.4%)16637 (58.6%)

Tag cloud dei nomi delle squadre

In [16]:
TAGCLOUD = 'tags.png'
import os
if not os.path.isfile(TAGCLOUD) or os.path.getmtime(TAGCLOUD) < os.path.getmtime('highscore.json'):
    from pytagcloud import create_tag_image, make_tags
    from pytagcloud.lang.counter import get_tag_counts
    import re

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

    names = ' '.join(scoredf['team_name'].str.strip().str.lower().tolist())
    names = names.encode('iso-8859-1', 'ignore')
    oknames = filter(lambda w: not notwanted.match(w), names.split(' '))

    counts = get_tag_counts(' '.join(oknames))

    # Solo quelli con almeno 10 occorrenze
    tags = make_tags(filter(lambda x: (x[1] >= 10), counts), maxsize=75)

    cloud = create_tag_image(tags, "tags.png", (900, 900), fontname="Neuton")

Tag cloud

In [17]:
plt.axis('off')
os = scoredf['operating_system'].value_counts().plot.pie(autopct='%.1f', radius=1.22,
                                                    explode=[.06*i*i for i in xrange(len(scoredf['operating_system'].unique()))],
                                                    figsize=(5,5), title='Sistemi operativi utilizzati')
In [ ]: