Analýza průběžného kvízu

In [1]:
# Výchozí parametry notebooku pro Papermill
FILENAME          = 'bizma1.json'
START             = '2020-09-24 20:00:00+0100'
DEADLINE          = '2020-10-11 23:59:00+0100'
SCORE_TO_PASS     = 15  # MARAST bodů
CLUSTER_THRESHOLD = 120 # minut
In [2]:
# Parameters
FILENAME = "bizma1.json"
START = "2020-09-24 20:00:00+0100"
DEADLINE = "2020-10-11 23:59:00+0100"
SCORE_TO_PASS = 15
In [3]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import fclusterdata

0. Načtení a předzpracování

Načtení dat z JSON formátu poskytovaného MARAST API, upravení datových typů sloupců a zpracování počátku/deadline.

In [4]:
df             = pd.read_json(FILENAME)
df.created_at  = pd.to_datetime(df.created_at, utc=True)
df.updated_at  = pd.to_datetime(df.updated_at, utc=True)
df.answered_at = pd.to_datetime(df.answered_at, utc=True)
df.score       = df.score.fillna(0).astype(np.int64)

start    = pd.to_datetime(START, utc=True)
deadline = pd.to_datetime(DEADLINE, utc=True)

Dopočtení dalších základních užitečných údajů:

  • doba od vytvoření otázky do zodpovězení,
  • doba zbývající do deadline.

Zahození dat, která pravděpodobně vznikla experimentováním nebo řešením singularit.

In [5]:
for idx, row in df.iterrows():
    # doba od vytvoření otázky do zodpovězení (v minutách)
    if row['answered_at']:
        df.at[idx, 'duration'] = (row['answered_at'] - row['created_at']).seconds / 60
        # doba od zodpovězení otázky do deadline (v minutách)
        df.at[idx, 'to_deadline_answer'] = (deadline - row['answered_at']).total_seconds() / 60

    # doba od vytvoření otázky do deadline (v minutách)
    df.at[idx, 'to_deadline'] = (deadline - row['created_at']).total_seconds() / 60

df = df[(df.created_at >= start) & (df.created_at <= deadline)]

Souhrn sloupců, pro kontrolu.

In [6]:
df.columns
Out[6]:
Index(['problem_id', 'anonymous_user_id', 'is_open', 'is_correctly_answered',
       'score', 'state', 'kind', 'solution', 'created_at', 'updated_at',
       'answered_at', 'is_modified', 'duration', 'to_deadline_answer',
       'to_deadline'],
      dtype='object')

Přehled datových typů sloupců.

In [7]:
df.dtypes
Out[7]:
problem_id                             int64
anonymous_user_id                     object
is_open                                 bool
is_correctly_answered                   bool
score                                  int64
state                                 object
kind                                  object
solution                              object
created_at               datetime64[ns, UTC]
updated_at               datetime64[ns, UTC]
answered_at              datetime64[ns, UTC]
is_modified                             bool
duration                             float64
to_deadline_answer                   float64
to_deadline                          float64
dtype: object

Extrakce studentů a příkladů vyskytujících se v datech.

In [8]:
students    = df.anonymous_user_id.drop_duplicates().values
problem_ids = df.problem_id.drop_duplicates().values

1. Celkový přehled odpovědí, studentů a příkladů

Celkové počty studentů, otázek a příkladů.

In [9]:
print('Počet studentů:              ', len(students))
print('Počet vygenerovaných otázek: ', len(df))
print('Počet zodpovězených otázek:  ', len(df[~df.is_open]))
print('Počet příkladů:              ', len(problem_ids))
print('Počet záporně ohodnocených:  ', len(df[df.score < 0]))
Počet studentů:               704
Počet vygenerovaných otázek:  15262
Počet zodpovězených otázek:   14927
Počet příkladů:               44
Počet záporně ohodnocených:   715

Další "globální" statistiky.

In [10]:
print(
    'Celková úspěšnost (správně zodpovězené / celkový počet odpovědí): ',
    len(df[(~df.is_open) & (df.is_correctly_answered)]) / len(df[~df.is_open])
)
print('Průměrný počet minut potřebaný k odeslání odpovědi:', df.duration.mean())
print('Medián minut potřebaných k odeslání odpovědi:', df.duration.median())
Celková úspěšnost (správně zodpovězené / celkový počet odpovědí):  0.7166878810209687
Průměrný počet minut potřebaný k odeslání odpovědi: 65.4737197065971
Medián minut potřebaných k odeslání odpovědi: 4.6

Kdy jsou studenti nejaktivnější co se otevírání nových otázek, resp. odesílání odpovědí, a denní hodiny týče?

In [11]:
ax = df.created_at.apply(lambda t: t.hour + t.minute / 60).hist(bins=24, figsize=(10, 5))
ax.set_xlabel('Hodina dne')
ax.set_ylabel('Počet vytvořených otázek')
ax.set_title('Otevírání nových otázek')

plt.xticks(range(25))
plt.show()
In [12]:
ax = df.answered_at.apply(lambda t: t.hour + t.minute / 60).hist(bins=24, figsize=(10, 5))
ax.set_xlabel('Hodina dne')
ax.set_ylabel('Počet odeslaných odpovědí')
ax.set_title('Zodpovídání otázek')

plt.xticks(range(25))
plt.show()

Jak je vytváření otázek závislé na blížícím se deadline?

In [13]:
data = df.to_deadline.apply(lambda t: t / 60)
maxh = int(np.ceil(data.max()))

ax = data.hist(bins=maxh, figsize=(12, 5))
ax.set_xlabel('Počet hodin zbývajících do deadline')
ax.set_ylabel('Počet vytvořených otázek')
ax.set_title('Aktivita studentů během doby přístupnosti kvízu')

plt.xticks(range(0, maxh, 24), rotation=90)
plt.show()

Jak dlouho studentům trvá na otázku odpovědět? Následující graf je ořezaný o několik outlierů (velmi dlouho otevřené otázky).

In [14]:
data = df[df.duration <= 10*df.duration.median()].duration
maxh = int(np.ceil(data.max()))

ax = data.hist(bins=maxh, figsize=(12, 5))
ax.set_xlabel('Počet minut na zodpovězení')
ax.set_ylabel('Počet otázek')
ax.set_title('Doba potřebaná k zodpovězení otázky')

plt.xticks(range(0, maxh, 1), rotation=90)
plt.show()

2. Statistiky příkladů a úspěšnost

Předpočtení statistik jednotlivých příkladů.

In [15]:
# Nový DataFrame
dp = pd.DataFrame(index=problem_ids, columns=[
    'displayed', 'kind', 'answers', 'correct', 'wrong', 'success_rate', 'median_time'
])

# Příklad po příkladu...
for p in problem_ids:
    answers = df[(df.problem_id == p) & (df.is_open == False)]
    
    if pd.isnull(dp.at[p, 'kind']):
        dp.at[p, 'kind'] = answers.iloc[0]['kind']
    
    dp.at[p, 'displayed'] = len(df[df.problem_id == p])
    dp.at[p, 'answers'] = len(answers)
    dp.at[p, 'correct'] = len(df[(df.problem_id == p) & (df.is_open == False) & (df.is_correctly_answered == True)])
    dp.at[p, 'wrong'] = dp.at[p, 'answers'] - dp.at[p, 'correct']
    dp.at[p, 'success_rate'] = dp.at[p, 'correct'] / dp.at[p, 'answers']
    dp.at[p, 'median_time'] = answers.duration.median()

Zobrazení výsledků, seřazeno podle míry úspěšnosti (od "nejtěžšího" příkladu k "nejlehčímu"). Index (první sloupec) je MARAST ID příkladu. V posledním sloupci je uveden medián počtu minut potřebných na zodpovězení otázky.

In [16]:
with pd.option_context('display.max_rows', None, 'display.max_columns', None):
    display(dp.sort_values(by=['success_rate']))
displayed kind answers correct wrong success_rate median_time
911 405 multichoice 396 131 265 0.330808 8.31667
957 399 multichoice 389 160 229 0.411311 4.61667
3847 7 multichoice 7 3 4 0.428571 48.8
912 386 multichoice 377 178 199 0.472149 8.75
5929 409 text_field 403 197 206 0.488834 8.61667
995 370 multichoice 352 173 179 0.491477 11.125
3846 2 multichoice 2 1 1 0.5 1142.77
748 404 multichoice 397 201 196 0.506297 7.7
910 377 multichoice 370 200 170 0.540541 7.75
1212 413 multichoice 402 218 184 0.542289 5.64167
745 401 multichoice 392 221 171 0.563776 6.65833
2381 411 multichoice 400 229 171 0.5725 3.90833
747 394 multichoice 385 221 164 0.574026 7.8
994 398 multichoice 388 239 149 0.615979 7.51667
959 380 multichoice 366 243 123 0.663934 6.625
2400 399 multichoice 394 266 128 0.675127 5.73333
909 389 multichoice 380 261 119 0.686842 4.95
3857 10 multichoice 10 7 3 0.7 10.5667
1211 399 multichoice 391 278 113 0.710997 11.3833
1219 7 multichoice 7 5 2 0.714286 2.06667
955 369 multichoice 359 259 100 0.721448 3.95
954 381 multichoice 374 275 99 0.735294 4.35833
744 374 multichoice 369 274 95 0.742547 3.48333
3848 4 multichoice 4 3 1 0.75 310.333
742 387 multichoice 381 288 93 0.755906 4.08333
2399 355 multichoice 349 265 84 0.759312 5.08333
1216 367 multichoice 357 272 85 0.761905 2.33333
956 401 multichoice 392 299 93 0.762755 4.16667
952 390 multichoice 378 296 82 0.783069 2.925
1221 387 multichoice 376 301 75 0.800532 4.85833
1210 385 multichoice 371 301 70 0.811321 5.05
960 372 multichoice 367 298 69 0.811989 3.4
1217 389 multichoice 385 313 72 0.812987 2.25
746 384 multichoice 378 311 67 0.822751 6.26667
951 401 multichoice 392 323 69 0.82398 3.36667
2394 394 multichoice 389 325 64 0.835476 3.98333
743 399 multichoice 389 331 58 0.8509 5.48333
958 389 multichoice 382 332 50 0.86911 3.44167
2396 396 multichoice 387 346 41 0.894057 2.81667
2418 381 multichoice 376 342 34 0.909574 4.45833
5928 392 text_field 373 350 23 0.938338 3.83333
741 405 multichoice 402 383 19 0.952736 2.175
938 396 multichoice 395 387 8 0.979747 1.11667
939 404 multichoice 394 392 2 0.994924 1.1

Závisí celková úspěšnost odpovídání na blízkosti deadline?

In [17]:
step = 6 * 60 # 3 hours
frame = pd.DataFrame(columns=['hours_to_deadline','success_ratio'])

for k in range(1, 8 * 7): # one week
    dsnap = df[(df.to_deadline_answer >= (k-1)*step) & (k*step > df.to_deadline_answer)]
    if len(dsnap) > 0:
        frame.at[k, 'hours_to_deadline'] = k*step
        frame.at[k, 'success_ratio'] = len(dsnap[dsnap.is_correctly_answered]) / len(dsnap)

maxt = frame.hours_to_deadline.max()
        
ax = frame.plot(
    x="hours_to_deadline", y="success_ratio",
    kind="bar", grid=True, legend=False
)

ax.set_xlabel('Počet minut na zodpovězení')
ax.set_ylabel('Počet otázek')
ax.set_title('Doba potřebaná k zodpovězení otázky')

plt.xticks(range(0, 8*7, 24), rotation=90)
plt.show()

4. Podrobnější analýza chování

Nejprve musíme extrahovat potřebná data. Zaznamenáme kolik odpovědí student vytvořil, kolik jich potřeboval na splnění kvízu, jak dlouho mu to od otevření kvízu trvalo, kolik času do deadline mu v okamžiku splnění kvízu zbývalo, úspěšnost jeho odpovědí a medián doby potřebné na odeslání jedné odpovědi.

In [18]:
ds = pd.DataFrame(index=students, columns=[
    'total_questions', 'total_score', 'questions_to_success',
    'minutes_to_success', 'minutes_left', 'success_rate', 'median_minutes_to_answer'
])

Nyní samotný výpočet.

In [19]:
for s in students:
    questions = df[df.anonymous_user_id == s].copy()
    
    ds.at[s, 'total_questions'] = len(questions)
    ds.at[s, 'total_score'] = questions.score.sum()
    
    score   = 0.0
    counter = 0
    success = 0
        
    for idx, q in questions.iterrows():
        counter += 1
        score   += q['score']
        
        if q['is_correctly_answered']:
            success += 1
    
        if q['answered_at']:
            questions.at[idx, 'minutes_to_answer'] = (q['answered_at'] - q['created_at']).total_seconds() / 60
    
        if score >= SCORE_TO_PASS:
            ds.at[s, 'questions_to_success'] = counter
            ds.at[s, 'minutes_to_success'] = (q['answered_at'] - questions.iloc[0]['answered_at']).total_seconds() / 60
            ds.at[s, 'minutes_left'] = (deadline - q['answered_at']).total_seconds() / 60
            break
    
    ds.at[s, 'success_rate'] = success / len(questions)
    ds.at[s, 'median_minutes_to_answer'] = questions.minutes_to_answer.dropna().median()

ds.questions_to_success = ds.questions_to_success.fillna(0).astype(np.int64)

Kontrola datových typů.

In [20]:
ds = ds.infer_objects()
ds.dtypes
Out[20]:
total_questions               int64
total_score                   int64
questions_to_success          int64
minutes_to_success          float64
minutes_left                float64
success_rate                float64
median_minutes_to_answer    float64
dtype: object

Náhled dat, z očividných důvodů nezobrazujeme všechny.

In [21]:
ds
Out[21]:
total_questions total_score questions_to_success minutes_to_success minutes_left success_rate median_minutes_to_answer
e544dc4c6917c0754dcd58843aa76d69 25 18 21 11400.116667 12234.9 0.640000 3.333333
9d5e1da74a11a7094aa732943d759c1e 18 11 0 NaN NaN 0.611111 3.716667
1e676a52576410475c8aae7c93b602c7 14 11 0 NaN NaN 0.785714 2.750000
153a4b0a56d423c73a3c5c6f30a5e24e 5 3 0 NaN NaN 0.600000 2.316667
1a00f70c7bf7ef84aba42c82109cf237 17 11 0 NaN NaN 0.647059 1.500000
... ... ... ... ... ... ... ...
61e27b7dd21cd431445da2bd4527fa1c 3 -1 0 NaN NaN 0.000000 3.683333
9dd0d33b1e1dbc93ddfc8f5741738551 8 2 0 NaN NaN 0.500000 4.491667
7006d26e27e95bf4fd011bd7268a8a27 8 7 0 NaN NaN 0.875000 2.000000
fe78e3a154c6b0b2934e16b54a315bdc 5 4 0 NaN NaN 0.800000 4.566667
196e7e871acfc0b296428195e7f8009a 8 4 0 NaN NaN 0.625000 1.266667

704 rows × 7 columns

Zcela základní ukazatale.

In [22]:
print("Počet studentů:      ", len(ds))
print("Z nich kvíz splnilo: ", len(ds[ds.total_score >= SCORE_TO_PASS]))
Počet studentů:       704
Z nich kvíz splnilo:  567

Studenti s nejvíce otázkami.

In [23]:
ds.sort_values(by='total_questions', ascending=False).head(10)
Out[23]:
total_questions total_score questions_to_success minutes_to_success minutes_left success_rate median_minutes_to_answer
6da2a4f039938adde238061197e26197 131 15 131 8304.000000 15310.566667 0.297710 1.133333
bbd2107106fe9bc5ee9e7c1a8af0f462 110 15 109 7110.350000 2875.833333 0.327273 1.600000
ce0b08abbe355d7cb793d0296dc8e4ce 68 45 16 7567.733333 16045.750000 0.220588 12.200000
8b82f9b4396f93efc15e939dc0f1f919 66 31 41 15454.700000 9252.433333 0.318182 2.733333
5596ce87a9056445cdc1c19a4f5657db 59 31 31 3181.016667 20693.300000 0.305085 4.266667
8d12d9f864203771370811731b5e3250 55 30 21 3309.500000 7565.633333 0.290909 4.016667
99e05f5b13f6e819d0d46b959e69ec55 54 15 54 6412.883333 204.916667 0.462963 2.775000
c9c989f0ce3e4353482ba5eab31b54b0 53 21 38 1279.050000 1689.000000 0.377358 3.508333
9f024fffe73d94810c24ac6b791e78cd 50 11 0 NaN NaN 0.440000 3.166667
1bbe0f595e6ed1ec8a98709766d0d6a7 48 30 26 23155.850000 159.166667 0.354167 14.791667

Kolik otázek studenti potřeboval ke splnění kvízu?

In [24]:
data = ds[ds.questions_to_success > 0].questions_to_success
maxo = int(data.max())

ax = data.hist(bins=(maxo+1), figsize=(10, 5))
ax.set_xlabel('Počet otázek')
ax.set_ylabel('Počet studentů')
ax.set_title('Počet otázek potřebných ke splnění kvízu')

plt.xticks(range(0, maxo, SCORE_TO_PASS), rotation=90)
plt.show()

Další statistiky počtu otázek potřebných ke splnění kvízu.

In [25]:
print(
    'Průměrný počet otázek potřebných ke splnění kvízu:',
    ds[ds.questions_to_success > 0].questions_to_success.mean()
)
print(
    'Medián počtu otázek potřebných ke splnění kvízu:',
    ds[ds.questions_to_success > 0].questions_to_success.median()
)
print(
    'Maximum počtu otázek potřebných ke splnění kvízu:',
    ds[ds.questions_to_success > 0].questions_to_success.max()
)
print(
    'Minimum počtu otázek potřebných ke splnění kvízu:',
    ds[ds.questions_to_success > 0].questions_to_success.min()
)
Průměrný počet otázek potřebných ke splnění kvízu: 21.797178130511465
Medián počtu otázek potřebných ke splnění kvízu: 20.0
Maximum počtu otázek potřebných ke splnění kvízu: 131
Minimum počtu otázek potřebných ke splnění kvízu: 15

Kolik času (od prvního otevření kvízu po jeho splnění) studentům řešení kvízu zabralo? Toto je hrubý nástřel, typicky bude obsahovat i noční dobu atp. I tak je ale výsledek zajímavý.

In [26]:
data = ds[ds.minutes_to_success > 0].minutes_to_success.apply(lambda t: t / 60)
maxd = int(np.ceil(data.max()) / 2)

ax = data.hist(bins=maxd, figsize=(18, 5))
ax.set_xlabel('Čas v hodinách')
ax.set_ylabel('Počet studentů')
ax.set_title('Čas potřebný ke splnění kvízu')

plt.xticks(range(0, 2*maxd, 4), rotation=90)
plt.show()

Kolik času studentům zbývalo do deadline v okamžik splnění kvízu?

In [27]:
data = ds[ds.minutes_to_success > 0].minutes_left.apply(lambda t: t / 60)
maxh = int(np.ceil(data.max()) / 2)

ax = data.hist(bins=maxh, figsize=(12, 5))
ax.set_xlabel('Počet hodin zbývajících do deadline')
ax.set_ylabel('Počet studentů')
ax.set_title('Kolik času zbývalo do deadline v okamžik splnění kvízu?')

plt.xticks(range(0, 2*maxh, 24), rotation=90)
plt.show()

Histogram úspěšnosti odpovídání.

In [28]:
ax = ds.success_rate.hist(bins=50, figsize=(10, 5))
ax.set_xlabel('Úspěšnost')
ax.set_ylabel('Počet studentů')
ax.set_title('Rozložení úspěšnosti odpovídání studentů')

plt.show()

Histogram mediánu času potřebného k odeslání odpovědi od vygenerování otázky.

In [29]:
ax = ds[ds.median_minutes_to_answer <= 60 * 6].median_minutes_to_answer.hist(bins=48, figsize=(10, 5))

ax.set_xlabel('Medián doby potřebné na zodpovězení (minuty)')
ax.set_ylabel('Počet studentů')
ax.set_title('Rozložení doby potřebné na zodpovězení')

plt.show()

Porovnání úspěšnosti odpovídání a času zbývajícího do deadline při splnění kvízu.

In [30]:
ax = ds.dropna(how='any', subset=['success_rate','minutes_left']).plot.hexbin(
    x='success_rate', y='minutes_left', gridsize=25, figsize=(10,7), sharex=False
)
ax.set_xlabel('Úspěšnost')
ax.set_ylabel('Čas zbývající do deadline (minuty)')
ax.set_title('Úspěšnost vs. čas zbývající do deadline')

plt.show()

Porovnání úspěšnosti odpovídání a mediánu doby potřebné k odeslání jedné odpovědi.

In [31]:
ax = ds[ds.median_minutes_to_answer <= 15].dropna(how='any', subset=['success_rate','median_minutes_to_answer']).plot.hexbin(
    x='success_rate', y='median_minutes_to_answer', gridsize=25, figsize=(10,7), sharex=False
)
ax.set_xlabel('Úspěšnost')
ax.set_ylabel('Medián doby na odpověď (minuty)')
ax.set_title('Úspěšnost vs. medián doby potřebné k zodpovězení')

plt.show()

5. Analýza počtu "sezení"

Rozšíříme tabulku studentů o další sloupce, pomocí clusterování se snažíme zjistit na kolik "sezení" student kvíz splnil.

In [32]:
for idx, row in ds.iterrows():
    answers = df[df.anonymous_user_id == idx].copy()
    
    if len(answers) <= 1:
        continue
    
    data = np.reshape(answers.to_deadline.values, (len(answers), 1))
    clusters = fclusterdata(data, t=CLUSTER_THRESHOLD, criterion='distance')
    answers['clusters'] = clusters
    
    ds.at[idx, 'nclusters'] = np.max(clusters)

ds.nclusters = ds.nclusters.fillna(0).astype(np.int64)

Hrubý náhled.

In [33]:
ds
Out[33]:
total_questions total_score questions_to_success minutes_to_success minutes_left success_rate median_minutes_to_answer nclusters
e544dc4c6917c0754dcd58843aa76d69 25 18 21 11400.116667 12234.9 0.640000 3.333333 6
9d5e1da74a11a7094aa732943d759c1e 18 11 0 NaN NaN 0.611111 3.716667 3
1e676a52576410475c8aae7c93b602c7 14 11 0 NaN NaN 0.785714 2.750000 1
153a4b0a56d423c73a3c5c6f30a5e24e 5 3 0 NaN NaN 0.600000 2.316667 2
1a00f70c7bf7ef84aba42c82109cf237 17 11 0 NaN NaN 0.647059 1.500000 2
... ... ... ... ... ... ... ... ...
61e27b7dd21cd431445da2bd4527fa1c 3 -1 0 NaN NaN 0.000000 3.683333 1
9dd0d33b1e1dbc93ddfc8f5741738551 8 2 0 NaN NaN 0.500000 4.491667 1
7006d26e27e95bf4fd011bd7268a8a27 8 7 0 NaN NaN 0.875000 2.000000 1
fe78e3a154c6b0b2934e16b54a315bdc 5 4 0 NaN NaN 0.800000 4.566667 1
196e7e871acfc0b296428195e7f8009a 8 4 0 NaN NaN 0.625000 1.266667 1

704 rows × 8 columns

Studenti nejvíce se vracející.

In [34]:
ds.sort_values(by='nclusters', ascending=False).head(10)
Out[34]:
total_questions total_score questions_to_success minutes_to_success minutes_left success_rate median_minutes_to_answer nclusters
adeeeca792975424d9a9d32e709aadd4 47 22 27 -15671.033333 17303.083333 0.382979 6.216667 14
1bbe0f595e6ed1ec8a98709766d0d6a7 48 30 26 23155.850000 159.166667 0.354167 14.791667 13
ea082fb97e7750e759f60eb96ce388d8 18 15 17 13108.550000 10919.533333 0.833333 690.750000 13
d041fddeb7b2aad95ac3dfbbd7b4fc08 19 15 18 9093.133333 1372.683333 0.842105 83.391667 11
595babfd0a7738025524707c40b9ae48 27 15 27 15633.050000 4951.183333 0.629630 13.616667 11
4af39136204ce7014cc8b2638bdbd5d6 25 10 0 NaN NaN 0.520000 17.983333 11
1bc9bd945e263731026542e2e0026d98 26 15 25 8291.066667 836.833333 0.692308 14.733333 11
29179eae472f561ee4639e5bcafab52c 19 15 18 3419.016667 4488.533333 0.789474 27.333333 10
516648a4c9d701813efef4778414122b 36 15 36 8686.400000 14607.383333 0.527778 2.641667 10
18b3991b5ec24ce451f3e7a1af888c30 33 15 33 23118.000000 648.000000 0.545455 2.900000 10
In [35]:
data = ds.nclusters
maxc = data.max()

ax = data.hist(bins=maxc, figsize=(10, 5))
ax.set_xlabel('Počet "sezení"')
ax.set_ylabel('Počet studentů')
ax.set_title('Na kolik "sezení" studenti kvíz vyplnili')

plt.show()

Porovnání ve vztahu k úspěšnosti.

In [36]:
ax = ds[ds.median_minutes_to_answer <= 15].dropna(how='any', subset=['success_rate','nclusters']).plot.hexbin(
    x='success_rate', y='nclusters', gridsize=25, figsize=(10,7), sharex=False
)
ax.set_xlabel('Úspěšnost')
ax.set_ylabel('Počet "sezení"')
ax.set_title('Úspěšnost vs. počet "sezení"')

plt.show()