09: Zpracování a analýza dat
Tento notebook je výukovým materiálem v předmětu BI-JUL.21 vyučovaném v zimním semestru akademického roku 2023/2024 Tomášem Kalvodou. Tvorba těchto materiálů byla podpořena NVS FIT.
Hlavní stránkou předmětu, kde jsou i další notebooky a zajímavé informace, je jeho Course Pages stránka.
versioninfo()
Julia Version 1.9.3 Commit bed2cd540a1 (2023-08-24 14:43 UTC) Build Info: Official https://julialang.org/ release Platform Info: OS: Linux (x86_64-linux-gnu) CPU: 8 × Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz WORD_SIZE: 64 LIBM: libopenlibm LLVM: libLLVM-14.0.6 (ORCJIT, skylake) Threads: 2 on 8 virtual cores
DataFrames.jl
1. Pro práci s daty jistě znáte Pythonovský nástroj pandas.
DataFrames.jl
je v podstatě Julia analog tohoto nástroje.
Pokud jste zvyklí pandas používat, může pro vás být užitečné porovnání pandas s Dataframes.jl
.
using DataFrames
DataFrame
?
1.1 Jak vytvořit DataFrame
lze vytvořit mnoha způsoby.
Můžeme začít s prázdnou tabulkou a postupně ji naplnit daty, nebo využít existující matici, nebo data načíst z externího souboru.
Prázdný DataFrame
vytvoříme velmi snadno:
DataFrame()
0 rows × 0 columns
NamedTuple
Keyword argumenty a Data v pojmenovaných sloupcích můžeme předat pomocí keyword argumentů (klíčové slovo je název sloupce, hodnota data):
DataFrame(course=["BI-LA1", "BI-DML", "BI-MA1", "BI-MA2"], semester=[1, 1, 2, 3], department=18105)
4 rows × 3 columns
course | semester | department | |
---|---|---|---|
String | Int64 | Int64 | |
1 | BI-LA1 | 1 | 18105 |
2 | BI-DML | 1 | 18105 |
3 | BI-MA1 | 2 | 18105 |
4 | BI-MA2 | 3 | 18105 |
Tímto způsobem bychom ovšem měli problém zadat data se sloupcích, jejichž názvy obsahují třeba speciální znaky jako mezery.
K tomu můžeme použít slovník, resp. dvojice (klíče mohou být řetězce nebo symboly -- ty jsou doporučené, místo mezer je vhodnější použít podtžítka :slovo_slovo
):
df = DataFrame("fiktivní postava" => ["Gandalf", "Harry Potter"], "kniha" => ["Pán prstenů", "Harry Potter a kámen mudrců"])
2 rows × 2 columns
fiktivní postava | kniha | |
---|---|---|
String | String | |
1 | Gandalf | Pán prstenů |
2 | Harry Potter | Harry Potter a kámen mudrců |
Další možností je použít NamedTuple
:
nt = (a = 1, b = 3)
(a = 1, b = 3)
nt[1], nt[2]
(1, 3)
nt[:a], nt[:b]
(1, 3)
Po sloupcích (pozor na jemný rozdíl od výše uvedeného způsobu):
DataFrame((a = [1,2,3], b = [4,5,6]))
3 rows × 2 columns
a | b | |
---|---|---|
Int64 | Int64 | |
1 | 1 | 4 |
2 | 2 | 5 |
3 | 3 | 6 |
Po řádcích:
DataFrame([(a = 1, b = 4), (a = 2, b = 5), (a = 3, b = 6)])
3 rows × 2 columns
a | b | |
---|---|---|
Int64 | Int64 | |
1 | 1 | 4 |
2 | 2 | 5 |
3 | 3 | 6 |
DataFrame([(a = 1, b = 4, c = nothing), (a = 2, b = 5, c = nothing), (a = 3, b = nothing, c = 6)])
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Int64 | Union… | Union… | |
1 | 1 | 4 | |
2 | 2 | 5 | |
3 | 3 | 6 |
Matice
K vytvoření DataFrame
můžeme použít i matici, jen musíme vyřešit pojmenování sloupců.
Automaticky (nutno zadat jako druhý argument :auto
) budou označeny jako x1
, x2
, atd.:
DataFrame(rand(2, 3), :auto)
2 rows × 3 columns
x1 | x2 | x3 | |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.237591 | 0.0940176 | 0.250011 |
2 | 0.315152 | 0.0200135 | 0.819533 |
DataFrame(rand(Int64, 2, 3), :auto)
2 rows × 3 columns
x1 | x2 | x3 | |
---|---|---|---|
Int64 | Int64 | Int64 | |
1 | 2884220508743408539 | 558676208905145960 | -5079403418050376869 |
2 | 4596193895810055742 | 1195210090384361372 | 1829307102220557273 |
V druhém argumentu případně můžeme zadat naše názvy sloupců:
DataFrame(rand(2, 3), ["col1", "col2", "col3"])
2 rows × 3 columns
col1 | col2 | col3 | |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.338029 | 0.213182 | 0.360838 |
2 | 0.685778 | 0.30592 | 0.674301 |
CSV.jl
V tomto notebooku budeme pro některé ukázky používat CSV anonymizovaný export z Grades předmětu BI-MA1 v semestru B212 (2021/2022).
Importu dat z CSV lze snadno docílit pomocí balíčku CSV.jl
, který přidáme standardně ] add CSV
a pak importujeme:
using CSV
Nyní stačí použít metodu read
z modulu CSV
, v prvním argumentu zadáme cestu k souboru a v druhém uvedeme požadovaný výstupní "formát", v našem případě DataFrame
:
df = CSV.read(joinpath("files", "bi-ma1-b212.csv"), DataFrame)
528 rows × 16 columns (omitted printing of 9 columns)
username | test1 | test2 | second_chance | activity | tests_total | gitlab | |
---|---|---|---|---|---|---|---|
String15 | Float64? | Float64? | Float64? | Float64? | Float64 | Float64? | |
1 | student001 | 2.0 | missing | missing | missing | 2.0 | missing |
2 | student002 | 9.0 | 16.0 | missing | missing | 25.0 | missing |
3 | student003 | 6.0 | 12.0 | missing | 5.0 | 18.0 | missing |
4 | student004 | 1.0 | missing | missing | 0.0 | 1.0 | missing |
5 | student005 | 4.0 | missing | missing | missing | 4.0 | missing |
6 | student006 | 2.0 | missing | missing | missing | 2.0 | missing |
7 | student007 | 9.5 | 6.0 | missing | missing | 15.5 | missing |
8 | student008 | 8.0 | 14.0 | 15.0 | missing | 25.0 | missing |
9 | student009 | 12.0 | 13.0 | missing | 4.0 | 25.0 | missing |
10 | student010 | 0.0 | missing | missing | missing | 0.0 | missing |
11 | student011 | 9.0 | 14.0 | 15.0 | missing | 25.0 | missing |
12 | student012 | missing | missing | missing | missing | 0.0 | missing |
13 | student013 | 17.5 | 18.5 | missing | 2.0 | 36.0 | missing |
14 | student014 | 7.5 | 14.0 | 15.0 | 2.0 | 25.0 | missing |
15 | student015 | 15.0 | 10.0 | missing | 5.0 | 25.0 | 0.5 |
16 | student016 | 12.0 | 15.0 | missing | 3.0 | 27.0 | missing |
17 | student017 | 6.0 | 16.5 | 22.0 | 3.0 | 25.0 | missing |
18 | student018 | 9.5 | 17.5 | missing | 0.5 | 27.0 | missing |
19 | student019 | 6.5 | 13.5 | 21.0 | 2.0 | 25.0 | missing |
20 | student020 | 11.5 | 9.5 | 10.5 | missing | 21.0 | missing |
21 | student021 | 10.0 | 13.5 | 19.0 | 4.0 | 25.0 | missing |
22 | student022 | missing | missing | missing | missing | 0.0 | missing |
23 | student023 | 2.0 | 8.5 | missing | missing | 10.5 | missing |
24 | student024 | 10.0 | 15.0 | missing | missing | 25.0 | missing |
25 | student025 | 20.0 | 18.0 | missing | 4.5 | 38.0 | 7.0 |
26 | student026 | 8.0 | 13.0 | 15.0 | 3.0 | 25.0 | 1.0 |
27 | student027 | 15.0 | 18.5 | missing | 3.5 | 33.5 | missing |
28 | student028 | 4.0 | 0.0 | missing | 2.0 | 4.0 | missing |
29 | student029 | 13.0 | 12.5 | missing | 2.5 | 25.5 | missing |
30 | student030 | 13.5 | 12.0 | missing | 2.0 | 25.5 | missing |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
K těmto datům se vrátíme podrobněji později. Samotnou tabulku občas použijeme v různých ukázkách.
DataFrame
1.2 Práce s Z částečného výpisu dat výše vyvstává hned několik otázek:
- Jaké sloupce jsou ještě k dispozici?
- Proč je typ pod některými sloupci s otazníkem?
- Co znamená missing?
Pojďme se vydat na průzkum. První, co můžeme zkusit, je podívat se na atributy naší instance (interaktivně pomocí TAB):
df.
df.username
528-element Vector{String15}: "student001" "student002" "student003" "student004" "student005" "student006" "student007" "student008" "student009" "student010" "student011" "student012" "student013" ⋮ "student517" "student518" "student519" "student520" "student521" "student522" "student523" "student524" "student525" "student526" "student527" "student528"
df.assessment
528-element Vector{Bool}: 0 1 0 0 0 0 0 1 1 0 1 0 1 ⋮ 0 1 0 0 0 0 1 1 1 1 1 1
Nebo k tomu můžeme použít metodu names
z modulu DataFrames
:
names(df)
16-element Vector{String}: "username" "test1" "test2" "second_chance" "activity" "tests_total" "gitlab" "assessment" "exam_test" "date" "oral_exam" "veto" "points_total" "mark" "tutor" "percentil"
Metoda eachcol
vrátí iterátor přes sloupce. Můžeme tak zjistit typ prvků sloupců:
eltype.(eachcol(df))
16-element Vector{Type}: String15 Union{Missing, Float64} Union{Missing, Float64} Union{Missing, Float64} Union{Missing, Float64} Float64 Union{Missing, Float64} Bool Union{Missing, Int64} Union{Missing, String7} Union{Missing, Float64} Union{Missing, Bool} Union{Missing, Float64} Union{Missing, String1} Union{Missing, String7} Int64
Odtud vidíme, co znamenají otazníky u typů prvků sloupců.
Prvky sloupce jsou složeného typu Union{Missing, T}
, v některých sloupcích mohou hodnoty chybět, obsahují hodnotu missing
.
Stručné informace můžeme získat pomocí metody describe
.
Samozřejmě občas uvedené statistiky nemají moc smysl.
describe(df)
16 rows × 7 columns
variable | mean | min | median | max | nmissing | eltype | |
---|---|---|---|---|---|---|---|
Symbol | Union… | Any | Union… | Any | Int64 | Type | |
1 | username | student001 | student528 | 0 | String15 | ||
2 | test1 | 8.45199 | 0.0 | 8.0 | 20.0 | 101 | Union{Missing, Float64} |
3 | test2 | 13.3188 | 0.0 | 14.0 | 20.0 | 219 | Union{Missing, Float64} |
4 | second_chance | 15.0588 | 0.0 | 15.0 | 23.5 | 460 | Union{Missing, Float64} |
5 | activity | 1.95629 | 0.0 | 2.0 | 5.0 | 242 | Union{Missing, Float64} |
6 | tests_total | 14.928 | 0.0 | 15.5 | 39.5 | 0 | Float64 |
7 | gitlab | 1.30769 | 0.0 | 1.0 | 7.0 | 489 | Union{Missing, Float64} |
8 | assessment | 0.395833 | 0 | 0.0 | 1 | 0 | Bool |
9 | exam_test | 16.7081 | 0 | 17.0 | 20 | 319 | Union{Missing, Int64} |
10 | date | --- | 16:50 | 319 | Union{Missing, String7} | ||
11 | oral_exam | 48.3026 | 28.0 | 50.0 | 60.0 | 338 | Union{Missing, Float64} |
12 | veto | 1.0 | 1 | 1.0 | 1 | 525 | Union{Missing, Bool} |
13 | points_total | 79.6217 | 55.0 | 80.0 | 103.5 | 339 | Union{Missing, Float64} |
14 | mark | A | F | 319 | Union{Missing, String1} | ||
15 | tutor | Honza | Tomáš | 8 | Union{Missing, String7} | ||
16 | percentil | 53.1136 | 23 | 50.0 | 100 | 0 | Int64 |
Výpis můžeme kontrolovat uvedením konkrétního rozsahu.
describe(df, cols=["test1", "test2"])
2 rows × 7 columns
variable | mean | min | median | max | nmissing | eltype | |
---|---|---|---|---|---|---|---|
Symbol | Float64 | Float64 | Float64 | Float64 | Int64 | Union | |
1 | test1 | 8.45199 | 0.0 | 8.0 | 20.0 | 101 | Union{Missing, Float64} |
2 | test2 | 13.3188 | 0.0 | 14.0 | 20.0 | 219 | Union{Missing, Float64} |
DataFrame
je tabulka, máme pro ní k dispozici podobné metody jako pro matice, na které se také lze dívat jako na "tabulky".
size(df)
(528, 16)
nrow(df)
528
ncol(df)
16
DataFrame
lze kopírovat pomocí metody copy
, nebo ho vyprázdnit pomocí metod empty
, resp. empty!
.
ex1 = DataFrame(rand(3, 2), :auto)
ex2 = copy(ex1)
3 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.127436 | 0.470857 |
2 | 0.149981 | 0.35907 |
3 | 0.968291 | 0.422546 |
empty!(ex2)
empty(ex1)
0 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 |
ex1
3 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.127436 | 0.470857 |
2 | 0.149981 | 0.35907 |
3 | 0.968291 | 0.422546 |
ex2
0 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 |
To by bylo pro začátek vše k základním vlastnostem. Pojďme nyní tabulky modifikovat, upravovat.
Indexování a přístup ke sloupcům
Indexování vychází z maticového zápisu a má podobné vlastnosti. Ke sloupcům můžeme přistupovat několika způsoby:
- pomocí názvu a tečky:
df.mark
,df."mark"
- pomocí indexace (nevytvoří kopii):
df[!, :mark]
,df[!, "mark"]
- pomocí indexace (vytvoří kopii):
df[:, :mark]
,df[:, "mark"]
Tímto způsobem můžeme data číst, ale i je modifikovat.
df.mark
528-element PooledArrays.PooledVector{Union{Missing, String1}, UInt32, Vector{UInt32}}: missing "C" missing missing missing missing missing "C" "D" missing "C" missing "A" ⋮ missing "D" missing missing missing missing "D" "E" "B" "F" "A" "D"
df.mark === df[!, :mark]
true
df.mark === df[:, :mark] # kopie!
false
Vedle toho nemusíme používat název sloupce, v pořádku je použít i pořadí. Například prvních deset řádků v druhém a třetím sloupci:
df[1:10, ["test2", "test1"]]
10 rows × 2 columns
test2 | test1 | |
---|---|---|
Float64? | Float64? | |
1 | missing | 2.0 |
2 | 16.0 | 9.0 |
3 | 12.0 | 6.0 |
4 | missing | 1.0 |
5 | missing | 4.0 |
6 | missing | 2.0 |
7 | 6.0 | 9.5 |
8 | 14.0 | 8.0 |
9 | 13.0 | 12.0 |
10 | missing | 0.0 |
Not
, Between
, Cols
, All
Dále můžeme vybírat pouze potřebné sloupce.
ex = DataFrame(rand(3, 5), :auto)
3 rows × 5 columns
x1 | x2 | x3 | x4 | x5 | |
---|---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | 0.264875 | 0.091569 | 0.925667 | 0.145848 | 0.811026 |
2 | 0.679459 | 0.497866 | 0.861729 | 0.572322 | 0.883065 |
3 | 0.823932 | 0.779033 | 0.972784 | 0.684341 | 0.469005 |
ex[:, Not(:x2)]
3 rows × 4 columns
x1 | x3 | x4 | x5 | |
---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | |
1 | 0.264875 | 0.925667 | 0.145848 | 0.811026 |
2 | 0.679459 | 0.861729 | 0.572322 | 0.883065 |
3 | 0.823932 | 0.972784 | 0.684341 | 0.469005 |
Not(:x2)
InvertedIndex{Symbol}(:x2)
ex[:, Not([:x2, :x3])]
3 rows × 3 columns
x1 | x4 | x5 | |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.264875 | 0.145848 | 0.811026 |
2 | 0.679459 | 0.572322 | 0.883065 |
3 | 0.823932 | 0.684341 | 0.469005 |
ex[:, Between(:x1, :x3)]
3 rows × 3 columns
x1 | x2 | x3 | |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.264875 | 0.091569 | 0.925667 |
2 | 0.679459 | 0.497866 | 0.861729 |
3 | 0.823932 | 0.779033 | 0.972784 |
ex[:, Cols(:x2, :x4)]
3 rows × 2 columns
x2 | x4 | |
---|---|---|
Float64 | Float64 | |
1 | 0.091569 | 0.145848 |
2 | 0.497866 | 0.572322 |
3 | 0.779033 | 0.684341 |
ex[:, All()]
3 rows × 5 columns
x1 | x2 | x3 | x4 | x5 | |
---|---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | 0.264875 | 0.091569 | 0.925667 | 0.145848 | 0.811026 |
2 | 0.679459 | 0.497866 | 0.861729 | 0.572322 | 0.883065 |
3 | 0.823932 | 0.779033 | 0.972784 | 0.684341 | 0.469005 |
Výše uvedené ukázky využívají funkcionalitu z DataFrames.jl
balíčku.
"Staré maticové" postupy samozřejmě fungují také, např.:
ex[:, [:x2, :x3]]
3 rows × 2 columns
x2 | x3 | |
---|---|---|
Float64 | Float64 | |
1 | 0.091569 | 0.925667 |
2 | 0.497866 | 0.861729 |
3 | 0.779033 | 0.972784 |
Přepisování dat
Údaje můžeme měnit několika způsoby. Vezměme si jednoduchou tabulku:
m = [ "α" 1 0.5; "β" 2 1.5; "γ" 3 2.5]
3×3 Matrix{Any}: "α" 1 0.5 "β" 2 1.5 "γ" 3 2.5
ex = DataFrame(m, ["a", "b", "c"])
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Any | |
1 | α | 1 | 0.5 |
2 | β | 2 | 1.5 |
3 | γ | 3 | 2.5 |
Změna jedné položky ( je velké ):
ex[1, :a] = "Ξ"
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Any | |
1 | Ξ | 1 | 0.5 |
2 | β | 2 | 1.5 |
3 | γ | 3 | 2.5 |
Přepsání všech hodnot ve sloupci c
:
ex[:, :c] = ["tau", "pi", "omega"]
3-element Vector{String}: "tau" "pi" "omega"
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Any | |
1 | Ξ | 1 | tau |
2 | β | 2 | pi |
3 | γ | 3 | omega |
Přepsání jednou hodnotou.
ex[:, :c] .= "ř"
3-element view(::Vector{Any}, :) with eltype Any: "ř" "ř" "ř"
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Any | |
1 | Ξ | 1 | ř |
2 | β | 2 | ř |
3 | γ | 3 | ř |
z = ex[:, :c]
3-element Vector{Any}: "ř" "ř" "ř"
z[2] = "pí"
"pí"
z
3-element Vector{Any}: "ř" "pí" "ř"
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Any | |
1 | Ξ | 1 | ř |
2 | β | 2 | ř |
3 | γ | 3 | ř |
z = ex[!, :c]
3-element Vector{Any}: "ř" "ř" "ř"
z[2] = "¿"
"¿"
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Any | |
1 | Ξ | 1 | ř |
2 | β | 2 | ¿ |
3 | γ | 3 | ř |
ex.c = [true, false, true]
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Bool | |
1 | Ξ | 1 | 1 |
2 | β | 2 | 0 |
3 | γ | 3 | 1 |
Přepsání hodnot ve sloupci a
danou hodnotou, ale pouze v řádcích, kde je hodnota ve sloupci b
lichá (zde opět používáme indexování bitovým vektorem, nebo "maskování", s kterým jsme se setkali dříve během semestru):
ex[isodd.(ex.b), :a] .= "λ"
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Bool | |
1 | λ | 1 | 1 |
2 | β | 2 | 0 |
3 | λ | 3 | 1 |
Občas je nutné projít tabulku řádek po řádku.
V tom případě můžeme iterovat přes index.
Například se pokusme změnit první sloupec na řetězec obsahující daný symbol se spodním indexem daným číslem ve sloupci b
v LaTeX notaci (tj. λ_1
atd.).
for row_id = axes(ex, 1) # 1:nrow(df)
ex[row_id, :a] = ex[row_id, :a] * "_" * string(ex[row_id, :b])
end
ex
3 rows × 3 columns
a | b | c | |
---|---|---|---|
Any | Any | Bool | |
1 | λ_1 | 1 | 1 |
2 | β_2 | 2 | 0 |
3 | λ_3 | 3 | 1 |
show
, first
, last
, view
)
Zobrazovaní (Pokud je DataFrame
příliš velký, tak při jeho zobrazení dojde k ořezání (řádků i sloupců). Toto chování můžeme přebít pomocí parametrů metody show
.
U našeho BI-ZMA příkladu je to lehce overkill.
show(df)
528×16 DataFrame Row │ username test1 test2 second_chance activity tests_total ⋯ │ String15 Float64? Float64? Float64? Float64? Float64 ⋯ ─────┼────────────────────────────────────────────────────────────────────────── 1 │ student001 2.0 missing missing missing 2.0 ⋯ 2 │ student002 9.0 16.0 missing missing 25.0 3 │ student003 6.0 12.0 missing 5.0 18.0 4 │ student004 1.0 missing missing 0.0 1.0 5 │ student005 4.0 missing missing missing 4.0 ⋯ 6 │ student006 2.0 missing missing missing 2.0 7 │ student007 9.5 6.0 missing missing 15.5 8 │ student008 8.0 14.0 15.0 missing 25.0 9 │ student009 12.0 13.0 missing 4.0 25.0 ⋯ 10 │ student010 0.0 missing missing missing 0.0 11 │ student011 9.0 14.0 15.0 missing 25.0 ⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱ 519 │ student519 0.0 missing missing missing 0.0 520 │ student520 1.0 missing missing missing 1.0 ⋯ 521 │ student521 7.0 missing missing missing 7.0 522 │ student522 missing missing missing missing 0.0 523 │ student523 9.5 11.5 15.0 2.0 25.0 524 │ student524 5.5 19.5 missing missing 25.0 ⋯ 525 │ student525 14.5 16.0 missing 2.0 30.5 526 │ student526 10.5 17.0 missing 2.0 27.5 527 │ student527 12.5 19.5 missing 4.0 32.0 528 │ student528 13.0 13.0 missing 1.0 26.0 ⋯ 10 columns and 507 rows omitted
show(df, allcols=true)
528×16 DataFrame Row │ username test1 test2 second_chance activity tests_total gitlab assessment exam_test date oral_exam veto points_total mark tutor percentil │ String15 Float64? Float64? Float64? Float64? Float64 Float64? Bool Int64? String7? Float64? Bool? Float64? String1? String7? Int64 ─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 1 │ student001 2.0 missing missing missing 2.0 missing false missing missing missing missing missing missing Honza 29 2 │ student002 9.0 16.0 missing missing 25.0 missing true 15 10:00 48.0 missing 73.0 C Irena 44 3 │ student003 6.0 12.0 missing 5.0 18.0 missing false missing missing missing missing missing missing Jitka 54 4 │ student004 1.0 missing missing 0.0 1.0 missing false missing missing missing missing missing missing Pavel 26 5 │ student005 4.0 missing missing missing 4.0 missing false missing missing missing missing missing missing Irena 36 6 │ student006 2.0 missing missing missing 2.0 missing false missing missing missing missing missing missing Jitka 29 7 │ student007 9.5 6.0 missing missing 15.5 missing false missing missing missing missing missing missing Honza 50 8 │ student008 8.0 14.0 15.0 missing 25.0 missing true 19 11:20 53.0 missing 78.0 C Irena 59 9 │ student009 12.0 13.0 missing 4.0 25.0 missing true 16 10:00 33.0 missing 62.0 D Jitka 71 10 │ student010 0.0 missing missing missing 0.0 missing false missing missing missing missing missing missing Honza 23 11 │ student011 9.0 14.0 15.0 missing 25.0 missing true 19 10:20 53.0 missing 78.0 C Jitka 71 ⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ 519 │ student519 0.0 missing missing missing 0.0 missing false missing missing missing missing missing missing Pavel 23 520 │ student520 1.0 missing missing missing 1.0 missing false missing missing missing missing missing missing Honza 26 521 │ student521 7.0 missing missing missing 7.0 missing false missing missing missing missing missing missing Jitka 42 522 │ student522 missing missing missing missing 0.0 missing false missing missing missing missing missing missing Irena 23 523 │ student523 9.5 11.5 15.0 2.0 25.0 missing true 17 10:15 40.0 missing 67.0 D Pavel 59 524 │ student524 5.5 19.5 missing missing 25.0 missing true 15 11:00 30.0 missing 55.0 E Honza 71 525 │ student525 14.5 16.0 missing 2.0 30.5 missing true 18 10:00 53.0 missing 85.5 B Pavel 86 526 │ student526 10.5 17.0 missing 2.0 27.5 missing true 13 --- missing missing missing F Honza 80 527 │ student527 12.5 19.5 missing 4.0 32.0 missing true 18 10:00 56.0 missing 92.0 A Pavel 90 528 │ student528 13.0 13.0 missing 1.0 26.0 0.0 true 15 10:00 42.0 missing 69.0 D Ivo 75 507 rows omitted
Toto by byl ještě větší overkill.
#show(df, allrows=true)
Případně můžeme prozkoumávat začátek a konec tabulky.
first(df, 10)
10 rows × 16 columns (omitted printing of 9 columns)
username | test1 | test2 | second_chance | activity | tests_total | gitlab | |
---|---|---|---|---|---|---|---|
String15 | Float64? | Float64? | Float64? | Float64? | Float64 | Float64? | |
1 | student001 | 2.0 | missing | missing | missing | 2.0 | missing |
2 | student002 | 9.0 | 16.0 | missing | missing | 25.0 | missing |
3 | student003 | 6.0 | 12.0 | missing | 5.0 | 18.0 | missing |
4 | student004 | 1.0 | missing | missing | 0.0 | 1.0 | missing |
5 | student005 | 4.0 | missing | missing | missing | 4.0 | missing |
6 | student006 | 2.0 | missing | missing | missing | 2.0 | missing |
7 | student007 | 9.5 | 6.0 | missing | missing | 15.5 | missing |
8 | student008 | 8.0 | 14.0 | 15.0 | missing | 25.0 | missing |
9 | student009 | 12.0 | 13.0 | missing | 4.0 | 25.0 | missing |
10 | student010 | 0.0 | missing | missing | missing | 0.0 | missing |
last(df, 10)
10 rows × 16 columns (omitted printing of 9 columns)
username | test1 | test2 | second_chance | activity | tests_total | gitlab | |
---|---|---|---|---|---|---|---|
String15 | Float64? | Float64? | Float64? | Float64? | Float64 | Float64? | |
1 | student519 | 0.0 | missing | missing | missing | 0.0 | missing |
2 | student520 | 1.0 | missing | missing | missing | 1.0 | missing |
3 | student521 | 7.0 | missing | missing | missing | 7.0 | missing |
4 | student522 | missing | missing | missing | missing | 0.0 | missing |
5 | student523 | 9.5 | 11.5 | 15.0 | 2.0 | 25.0 | missing |
6 | student524 | 5.5 | 19.5 | missing | missing | 25.0 | missing |
7 | student525 | 14.5 | 16.0 | missing | 2.0 | 30.5 | missing |
8 | student526 | 10.5 | 17.0 | missing | 2.0 | 27.5 | missing |
9 | student527 | 12.5 | 19.5 | missing | 4.0 | 32.0 | missing |
10 | student528 | 13.0 | 13.0 | missing | 1.0 | 26.0 | 0.0 |
Pro rychlé prozkoumávání částí tabulky slouží metoda view
, resp. makro @view
, která jen část tabulky zobrazí.
Nevytváří nový objekt, měla by být efektivnější.
view(df, 100:150, [:username, :points_total, :mark])
51 rows × 3 columns
username | points_total | mark | |
---|---|---|---|
String15 | Float64? | String1? | |
1 | student100 | missing | missing |
2 | student101 | missing | missing |
3 | student102 | 85.0 | B |
4 | student103 | missing | missing |
5 | student104 | missing | missing |
6 | student105 | 64.0 | D |
7 | student106 | missing | missing |
8 | student107 | 90.0 | A |
9 | student108 | missing | missing |
10 | student109 | 84.0 | B |
11 | student110 | missing | missing |
12 | student111 | missing | missing |
13 | student112 | missing | missing |
14 | student113 | missing | F |
15 | student114 | missing | missing |
16 | student115 | missing | missing |
17 | student116 | 80.0 | B |
18 | student117 | missing | missing |
19 | student118 | missing | F |
20 | student119 | missing | F |
21 | student120 | missing | missing |
22 | student121 | missing | missing |
23 | student122 | 80.0 | B |
24 | student123 | missing | missing |
25 | student124 | missing | missing |
26 | student125 | missing | missing |
27 | student126 | missing | missing |
28 | student127 | 80.0 | B |
29 | student128 | missing | missing |
30 | student129 | missing | missing |
⋮ | ⋮ | ⋮ | ⋮ |
@view df[10:15, [:username, :mark]]
6 rows × 2 columns
username | mark | |
---|---|---|
String15 | String1? | |
1 | student010 | missing |
2 | student011 | C |
3 | student012 | missing |
4 | student013 | A |
5 | student014 | B |
6 | student015 | B |
Přidávání a odebírání sloupců
K přidávání sloupců slouží metoda insertcols!
:
ex = DataFrame(rand(2, 2), :auto)
2 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.142516 | 0.965487 |
2 | 0.00505577 | 0.571855 |
insertcols!(ex, 1, :a => pi)
2 rows × 3 columns
a | x1 | x2 | |
---|---|---|---|
Irration… | Float64 | Float64 | |
1 | π | 0.142516 | 0.965487 |
2 | π | 0.00505577 | 0.571855 |
insertcols!(ex, 3, :b => "⊕")
2 rows × 4 columns
a | x1 | b | x2 | |
---|---|---|---|---|
Irration… | Float64 | String | Float64 | |
1 | π | 0.142516 | ⊕ | 0.965487 |
2 | π | 0.00505577 | ⊕ | 0.571855 |
insertcols!(ex, 5, :c => [1, 2])
2 rows × 5 columns
a | x1 | b | x2 | c | |
---|---|---|---|---|---|
Irration… | Float64 | String | Float64 | Int64 | |
1 | π | 0.142516 | ⊕ | 0.965487 | 1 |
2 | π | 0.00505577 | ⊕ | 0.571855 | 2 |
Ale lze použít i prosté indexování:
ex[:, :d] = [42, 42]
ex
2 rows × 6 columns
a | x1 | b | x2 | c | d | |
---|---|---|---|---|---|---|
Irration… | Float64 | String | Float64 | Int64 | Int64 | |
1 | π | 0.142516 | ⊕ | 0.965487 | 1 | 42 |
2 | π | 0.00505577 | ⊕ | 0.571855 | 2 | 42 |
Mazání sloupců musíme provést pomocí indexace, neexistuje metoda "dropcolumns!
".
ex[:, Not(:b)]
2 rows × 5 columns
a | x1 | x2 | c | d | |
---|---|---|---|---|---|
Irration… | Float64 | Float64 | Int64 | Int64 | |
1 | π | 0.142516 | 0.965487 | 1 | 42 |
2 | π | 0.00505577 | 0.571855 | 2 | 42 |
Přidávání a odebírání řádků
Vytvořme si zase testovací tabulku:
ex = DataFrame(rand(2, 2), :auto)
2 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.974232 | 0.598728 |
2 | 0.655042 | 0.634699 |
Přidat řádek lze opět několika způsoby. Nejpřirozenější je asi metoda push!
:
push!(ex, [1.0, 2.0])
3 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.974232 | 0.598728 |
2 | 0.655042 | 0.634699 |
3 | 1.0 | 2.0 |
push!(ex, (x1 = 0.3, x2 = 0.5))
4 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.974232 | 0.598728 |
2 | 0.655042 | 0.634699 |
3 | 1.0 | 2.0 |
4 | 0.3 | 0.5 |
push!(ex, (x2 = 7, x1 = 5))
6 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.974232 | 0.598728 |
2 | 0.655042 | 0.634699 |
3 | 1.0 | 2.0 |
4 | 0.3 | 0.5 |
5 | 0.5 | 7.0 |
6 | 5.0 | 7.0 |
Pomocí append!
můžeme spojovat několik tabulek "vertikálně" dohromady:
append!(ex, DataFrame(rand(10, 2), :auto))
16 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.974232 | 0.598728 |
2 | 0.655042 | 0.634699 |
3 | 1.0 | 2.0 |
4 | 0.3 | 0.5 |
5 | 0.5 | 7.0 |
6 | 5.0 | 7.0 |
7 | 0.642997 | 0.0414806 |
8 | 0.0583266 | 0.237347 |
9 | 0.93793 | 0.249314 |
10 | 0.737453 | 0.495015 |
11 | 0.337625 | 0.0557319 |
12 | 0.344675 | 0.674127 |
13 | 0.0909182 | 0.0982035 |
14 | 0.12483 | 0.850773 |
15 | 0.219731 | 0.093136 |
16 | 0.629028 | 0.882152 |
Mazat řádky můžeme explicitně pomocí delete!
:
delete!(ex, 3)
15 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.974232 | 0.598728 |
2 | 0.655042 | 0.634699 |
3 | 0.3 | 0.5 |
4 | 0.5 | 7.0 |
5 | 5.0 | 7.0 |
6 | 0.642997 | 0.0414806 |
7 | 0.0583266 | 0.237347 |
8 | 0.93793 | 0.249314 |
9 | 0.737453 | 0.495015 |
10 | 0.337625 | 0.0557319 |
11 | 0.344675 | 0.674127 |
12 | 0.0909182 | 0.0982035 |
13 | 0.12483 | 0.850773 |
14 | 0.219731 | 0.093136 |
15 | 0.629028 | 0.882152 |
delete!(ex, [1, 2, 3])
12 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.5 | 7.0 |
2 | 5.0 | 7.0 |
3 | 0.642997 | 0.0414806 |
4 | 0.0583266 | 0.237347 |
5 | 0.93793 | 0.249314 |
6 | 0.737453 | 0.495015 |
7 | 0.337625 | 0.0557319 |
8 | 0.344675 | 0.674127 |
9 | 0.0909182 | 0.0982035 |
10 | 0.12483 | 0.850773 |
11 | 0.219731 | 0.093136 |
12 | 0.629028 | 0.882152 |
ex.x1 .> 0.5
12-element BitVector: 0 1 1 0 1 1 0 0 0 0 0 1
delete!(ex, ex.x1 .> 0.5)
7 rows × 2 columns
x1 | x2 | |
---|---|---|
Float64 | Float64 | |
1 | 0.5 | 7.0 |
2 | 0.0583266 | 0.237347 |
3 | 0.337625 | 0.0557319 |
4 | 0.344675 | 0.674127 |
5 | 0.0909182 | 0.0982035 |
6 | 0.12483 | 0.850773 |
7 | 0.219731 | 0.093136 |
To zdaleka není všechno
Výše jsme shrnuli zřejmě ty nejužitečnější metody a techniky.
Tím ale možnosti DataFrames.jl
zdaleka nekončí.
Zvídavému čtenáři doporučujeme prolétnout dokumentaci.
Na tomto místě snad jen upozorněme na následující:
- Joins.
- Grouping.
- Přehledné porovnání metod s pandas/R/Stata.
- Balíček
Query.jl
umožňující pracovat s různými datovými zdroji. - Balíček
DataFramesMeta.jl
poskytující užitečná makra pro práci s tabulkami. - Cheat Sheet
Velmi sofistikované transformace dat v tabulkách lze také provádět pomocí následujících metod:
combine
select
/select!
transform
/transform!
Jejich podrobný výklad je už nad rámec tohoto kurzu.
2. Cvičení: analýza BI-MA1 B212 (a B222)
Pojďme se podrobněji podívat na data z předmětu BI-MA1 v semestru B212, tedy první běh předmětu. Tehdejší pravidla pro získání zápočtu a složení zkoušky lze dohledat v gitlabu.
Nejprve znovu načteme data z CSV souboru, který je syrovým exportem z Grades, kde jsou anonymizováni uživatelská jména studentů.
using CSV
df1 = CSV.read(joinpath("files", "bi-ma1-b212.csv"), DataFrame)
df2 = CSV.read(joinpath("files", "bi-ma1-b222.csv"), DataFrame)
760 rows × 17 columns (omitted printing of 10 columns)
username | test1 | test2 | test3 | second_chance | activity | tests_total | |
---|---|---|---|---|---|---|---|
String15 | Float64? | Float64? | Float64? | Float64? | Float64? | Float64? | |
1 | student0001 | 1.5 | 11.0 | 9.0 | 17.0 | missing | 25.0 |
2 | student0002 | 3.0 | 15.0 | 18.5 | missing | 2.0 | 36.5 |
3 | student0003 | missing | missing | missing | missing | missing | 0.0 |
4 | student0004 | missing | missing | missing | missing | missing | 0.0 |
5 | student0005 | missing | missing | missing | missing | missing | 0.0 |
6 | student0006 | missing | missing | missing | missing | missing | 0.0 |
7 | student0007 | 3.0 | 12.0 | 18.5 | missing | 1.5 | 33.5 |
8 | student0008 | 1.0 | 6.0 | 14.0 | 18.5 | 1.0 | 25.0 |
9 | student0009 | 2.0 | 16.5 | 20.0 | missing | 1.0 | 38.5 |
10 | student0010 | 1.0 | 10.5 | 13.5 | missing | missing | 25.0 |
11 | student0011 | 2.5 | 7.5 | 12.0 | 14.0 | 2.0 | 22.0 |
12 | student0012 | 0.5 | 14.5 | 10.5 | missing | missing | 25.5 |
13 | student0013 | 2.5 | 6.0 | 13.5 | 12.5 | 3.0 | 22.0 |
14 | student0014 | 3.0 | 10.5 | missing | missing | missing | 13.5 |
15 | student0015 | 0.0 | 0.0 | missing | missing | missing | 0.0 |
16 | student0016 | 3.0 | 6.0 | missing | missing | missing | 9.0 |
17 | student0017 | 0.0 | 1.0 | missing | missing | missing | 1.0 |
18 | student0018 | 1.0 | 15.0 | 13.0 | missing | missing | 29.0 |
19 | student0019 | missing | missing | missing | missing | missing | 0.0 |
20 | student0020 | 2.5 | 18.0 | 10.5 | missing | 1.0 | 31.0 |
21 | student0021 | missing | missing | missing | missing | missing | 0.0 |
22 | student0022 | 1.0 | 2.5 | missing | missing | missing | 3.5 |
23 | student0023 | 3.0 | 14.5 | 14.0 | missing | missing | 31.5 |
24 | student0024 | 2.5 | 16.0 | 7.5 | missing | 4.0 | 26.0 |
25 | student0025 | 1.0 | missing | missing | missing | missing | 1.0 |
26 | student0026 | 1.0 | 7.5 | missing | missing | missing | 8.5 |
27 | student0027 | 0.5 | 9.0 | 16.5 | missing | missing | 26.0 |
28 | student0028 | 3.0 | 13.5 | 18.5 | missing | 4.0 | 35.0 |
29 | student0029 | 3.0 | 5.5 | 12.5 | 14.0 | missing | 21.0 |
30 | student0030 | 0.5 | missing | missing | missing | 0.5 | 0.5 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
describe(df1)
16 rows × 7 columns
variable | mean | min | median | max | nmissing | eltype | |
---|---|---|---|---|---|---|---|
Symbol | Union… | Any | Union… | Any | Int64 | Type | |
1 | username | student001 | student528 | 0 | String15 | ||
2 | test1 | 8.45199 | 0.0 | 8.0 | 20.0 | 101 | Union{Missing, Float64} |
3 | test2 | 13.3188 | 0.0 | 14.0 | 20.0 | 219 | Union{Missing, Float64} |
4 | second_chance | 15.0588 | 0.0 | 15.0 | 23.5 | 460 | Union{Missing, Float64} |
5 | activity | 1.95629 | 0.0 | 2.0 | 5.0 | 242 | Union{Missing, Float64} |
6 | tests_total | 14.928 | 0.0 | 15.5 | 39.5 | 0 | Float64 |
7 | gitlab | 1.30769 | 0.0 | 1.0 | 7.0 | 489 | Union{Missing, Float64} |
8 | assessment | 0.395833 | 0 | 0.0 | 1 | 0 | Bool |
9 | exam_test | 16.7081 | 0 | 17.0 | 20 | 319 | Union{Missing, Int64} |
10 | date | --- | 16:50 | 319 | Union{Missing, String7} | ||
11 | oral_exam | 48.3026 | 28.0 | 50.0 | 60.0 | 338 | Union{Missing, Float64} |
12 | veto | 1.0 | 1 | 1.0 | 1 | 525 | Union{Missing, Bool} |
13 | points_total | 79.6217 | 55.0 | 80.0 | 103.5 | 339 | Union{Missing, Float64} |
14 | mark | A | F | 319 | Union{Missing, String1} | ||
15 | tutor | Honza | Tomáš | 8 | Union{Missing, String7} | ||
16 | percentil | 53.1136 | 23 | 50.0 | 100 | 0 | Int64 |
size(df1)
(528, 16)
nrow(df1)
528
describe(df2)
17 rows × 7 columns
variable | mean | min | median | max | nmissing | eltype | |
---|---|---|---|---|---|---|---|
Symbol | Union… | Any | Union… | Any | Int64 | Type | |
1 | username | student0001 | student0760 | 0 | String15 | ||
2 | test1 | 1.69818 | 0.0 | 1.5 | 3.0 | 99 | Union{Missing, Float64} |
3 | test2 | 10.6672 | 0.0 | 11.0 | 20.0 | 132 | Union{Missing, Float64} |
4 | test3 | 12.6893 | 0.0 | 13.5 | 20.0 | 245 | Union{Missing, Float64} |
5 | second_chance | 14.5402 | 5.0 | 15.0 | 22.0 | 673 | Union{Missing, Float64} |
6 | activity | 2.27454 | -6.0 | 2.0 | 5.0 | 383 | Union{Missing, Float64} |
7 | tests_total | 19.1148 | 0.0 | 24.5 | 43.0 | 2 | Union{Missing, Float64} |
8 | gitlab | 2.23684 | 1 | 1.0 | 12 | 722 | Union{Missing, Int64} |
9 | assessment | 0.497361 | 0 | 0.0 | 1 | 2 | Union{Missing, Bool} |
10 | exam_test | 16.4377 | -1 | 17.0 | 20 | 383 | Union{Missing, Int64} |
11 | date | --- | CT 14:30 | 383 | Union{Missing, String15} | ||
12 | oral_exam | 46.8988 | 20.0 | 48.0 | 57.0 | 419 | Union{Missing, Float64} |
13 | veto | 1.0 | 1 | 1.0 | 1 | 757 | Union{Missing, Bool} |
14 | points_total | 80.3235 | 54.5 | 80.5 | 105.0 | 420 | Union{Missing, Float64} |
15 | mark | A | F | 383 | Union{Missing, String1} | ||
16 | tutor | Irena | Petr | 2 | Union{Missing, String7} | ||
17 | percentil | 50.9513 | 12 | 50.0 | 100 | 0 | Int64 |
size(df2)
(760, 17)
nrow(df2)
760
2.1 Základní údaje
Nejprve prozkoumejte dostupné sloupce a pokuste se zjistit základní údaje jako
- Kolik studentů mělo předmět zapsáno?
- Kolik studentů získalo zápočet a dokončilo předmět?
- Kolik studentů bylo v jakém ročníku? (V prvním běhu BI-MA1 toto nemá smysl.)
- ...
Počet studentů.
nrow(df1)
528
nrow(df2)
760
Kolik studentů má zápočet?
sum(df1.assessment)
209
length(filter(isone, df1.assessment))
209
sum(df2.assessment)
missing
df2.assessment
760-element Vector{Union{Missing, Bool}}: true true false false false false true true true true false true false ⋮ true false false true true true true false true true false true
filter(x -> !ismissing(x), df2.assessment)
758-element Vector{Union{Missing, Bool}}: true true false false false false true true true true false true false ⋮ true false false true true true true false true true false true
filter(x -> !ismissing(x), df2.assessment) |> sum
377
sum(dropmissing(df2, :assessment).assessment)
377
coalesce.(df2.assessment, 0) |> sum
377
Tj. průchodnost zápočtu.
sum(df1.assessment) / nrow(df1)
0.3958333333333333
sum(coalesce.(df2.assessment, 0)) / nrow(df2)
0.49605263157894736
Počet vet v tabulce.
sum(df1.veto)
missing
missing + 1 # aha! pozor na missing hodnoty
missing
sum(dropmissing(df1[:, Cols(:veto)]).veto)
3
sum(filter(x -> !ismissing(x), df1.veto))
3
sum(dropmissing(df1, :veto).veto)
3
sum(dropmissing(df2, :veto).veto)
3
Získalo zápočet a úspěšně dokončilo předmět:
length(filter(x -> x != "F", dropmissing(df1, :mark).mark))
189
sum(dropmissing(df1, :mark).mark .!= "F")
189
sum(dropmissing(df2, :mark).mark .!= "F")
340
2.2 Zápočtové písemky, kvízy a semestr
Poté se podívejme podrobněji jak probíhalo získávání zápočtů.
- Prozkoumejte výsledky jednotlivých zápočtových písemek, vytvořte historgramy výsledků.
- Kolika studentům zápočet "těsně" unikl?
- Kolik studentů "odpadlo" už v první polovině semestru?
- ...
using PyPlot, Statistics
Histogram prvního zápočtové testu.
plt.grid()
hist(filter(x -> !ismissing(x), df1.test1), bins=[ -0.49 + j for j in 0:21]);
skipmissing(df1.test1)
skipmissing(Union{Missing, Float64}[2.0, 9.0, 6.0, 1.0, 4.0, 2.0, 9.5, 8.0, 12.0, 0.0 … 0.0, 1.0, 7.0, missing, 9.5, 5.5, 14.5, 10.5, 12.5, 13.0])
collect(skipmissing(df1.test1))
427-element Vector{Float64}: 2.0 9.0 6.0 1.0 4.0 2.0 9.5 8.0 12.0 0.0 9.0 17.5 7.5 ⋮ 2.5 6.5 8.0 0.0 1.0 7.0 9.5 5.5 14.5 10.5 12.5 13.0
mean(skipmissing(df1.test1))
8.451990632318502
median(skipmissing(df1.test1))
8.0
length(collect(skipmissing(df1.test1)))
427
nrow(df1) - length(df1.test1[ismissing.(df1.test1)])
427
V druhém běhu MA1 nelze přímo porovnávat první testy.
plt.grid()
hist(filter(x -> !ismissing(x), df2.test1), bins=[ -0.49 + j for j in 0:4]);
Histogram druhého zápočtového testu.
plt.grid()
hist(filter(x -> !ismissing(x), df1.test2), bins=[ -0.49 + j for j in 0:21]);
Druhý a třetí test v druhém běhu MA1.
plt.grid()
hist(filter(x -> !ismissing(x), df2.test2), bins=[ -0.49 + j for j in 0:21]);
plt.grid()
hist(filter(x -> !ismissing(x), df2.test3), bins=[ -0.49 + j for j in 0:21]);
df1.test2 |> skipmissing |> collect |> length
309
df2.test2 |> skipmissing |> collect |> length
628
df2.test3 |> skipmissing |> collect |> length
515
Počet studentů, kteří získali zápočet bez opravné zápočtové písemky.
sum(coalesce.(df1.test1 + df1.test2, 0) .>= 25.)
165
sum(coalesce.(df2.test1 + df2.test2 + df2.test3, 0) .>= 25.)
325
Počet studentů s alespoň 20 body ze zápočtových písemek.
nrow(df1[coalesce.(df1.tests_total .>= 20, false), :])
234
nrow(df2[coalesce.(df2.tests_total .>= 20, false), :])
418
Histogram opravné zápočtové písemky.
plt.grid()
hist(filter(x -> !ismissing(x), df1.second_chance), bins=[ -0.49 + j for j in 0:25]);
plt.grid()
hist(filter(x -> !ismissing(x), df2.second_chance), bins=[ -0.49 + j for j in 0:25]);
Histogram celkového počtu bodů ze semestru.
plt.grid()
hist(filter(x -> !ismissing(x), df1.tests_total), bins=[ -0.49 + j for j in 0:41]);
plt.grid()
hist(filter(x -> !ismissing(x), df2.tests_total), bins=[ -0.49 + j for j in 0:41]);
Histogram celkového počtu bodů z písemek ze semestru ("zub milosti").
plt.grid()
hist(filter(x -> !ismissing(x), df1.points_total), bins=[ -0.49 + j for j in 55:106]);
plt.grid()
hist(filter(x -> !ismissing(x), df2.points_total), bins=[ -0.49 + j for j in 55:106]);
Úspěšnost zisku zápočtu podle cvičících.
df = combine(groupby(df1, :tutor),
nrow => "studenti",
:assessment => sum => "zápočty",
:test1 => (x -> sum(coalesce.(x, 0) .> 0)) => "psali 1. test"
)
df[:, "propustnost"] = df[:, "zápočty"] ./ df[:, "studenti"]
df
7 rows × 5 columns
tutor | studenti | zápočty | psali 1. test | propustnost | |
---|---|---|---|---|---|
String7? | Int64 | Int64 | Int64 | Float64 | |
1 | Honza | 88 | 26 | 58 | 0.295455 |
2 | Irena | 95 | 30 | 63 | 0.315789 |
3 | Jitka | 95 | 34 | 78 | 0.357895 |
4 | Pavel | 96 | 38 | 78 | 0.395833 |
5 | missing | 8 | 0 | 0 | 0.0 |
6 | Tomáš | 98 | 69 | 90 | 0.704082 |
7 | Ivo | 48 | 12 | 39 | 0.25 |
df = combine(groupby(df2, :tutor),
nrow => "studenti",
:assessment => (x -> sum(collect(skipmissing(x)))) => "zápočty",
:test1 => (x -> sum(coalesce.(x, 0) .> 0)) => "psali 1. test"
)
df[:, "propustnost"] = df[:, "zápočty"] ./ df[:, "studenti"]
df
9 rows × 5 columns
tutor | studenti | zápočty | psali 1. test | propustnost | |
---|---|---|---|---|---|
String7? | Int64 | Int64 | Int64 | Float64 | |
1 | Jan V | 146 | 64 | 115 | 0.438356 |
2 | Pavel | 149 | 84 | 120 | 0.563758 |
3 | Jitka | 73 | 41 | 62 | 0.561644 |
4 | Petr | 99 | 39 | 64 | 0.393939 |
5 | Jiřina | 68 | 36 | 54 | 0.529412 |
6 | Irena | 74 | 41 | 68 | 0.554054 |
7 | Jarda | 100 | 57 | 90 | 0.57 |
8 | Jan S | 49 | 15 | 33 | 0.306122 |
9 | missing | 2 | 0 | 0 | 0.0 |
2.3 Zkouškové období
V BI-MA1 hodně rozdílné vůči BI-ZMA.
Histogram známek.
combine(groupby(df1, :mark), nrow => "studenti") |> sort
7 rows × 2 columns
mark | studenti | |
---|---|---|
String1? | Int64 | |
1 | A | 42 |
2 | B | 57 |
3 | C | 53 |
4 | D | 32 |
5 | E | 5 |
6 | F | 20 |
7 | missing | 319 |
combine(groupby(df2, :mark), nrow => "studenti") |> sort
7 rows × 2 columns
mark | studenti | |
---|---|---|
String1? | Int64 | |
1 | A | 79 |
2 | B | 104 |
3 | C | 110 |
4 | D | 41 |
5 | E | 6 |
6 | F | 37 |
7 | missing | 383 |
sort(combine(groupby(df1[coalesce.(df1.gitlab, 0) .> 0 ,:], :mark), nrow => "studenti"), :mark)
6 rows × 2 columns
mark | studenti | |
---|---|---|
String1? | Int64 | |
1 | A | 12 |
2 | B | 7 |
3 | C | 8 |
4 | D | 2 |
5 | F | 4 |
6 | missing | 5 |
sort(combine(groupby(df2[coalesce.(df2.gitlab, 0) .> 0 ,:], :mark), nrow => "studenti"), :mark)
6 rows × 2 columns
mark | studenti | |
---|---|---|
String1? | Int64 | |
1 | A | 13 |
2 | B | 12 |
3 | C | 4 |
4 | D | 1 |
5 | F | 2 |
6 | missing | 6 |
sort(combine(groupby(df1[coalesce.(df1.activity, 0) .> 2 ,:], :mark), nrow => "studenti"), :mark)
6 rows × 2 columns
mark | studenti | |
---|---|---|
String1? | Int64 | |
1 | A | 29 |
2 | B | 25 |
3 | C | 13 |
4 | D | 10 |
5 | F | 4 |
6 | missing | 15 |
sort(combine(groupby(df2[coalesce.(df2.activity, 0) .> 2 ,:], :mark), nrow => "studenti"), :mark)
6 rows × 2 columns
mark | studenti | |
---|---|---|
String1? | Int64 | |
1 | A | 47 |
2 | B | 39 |
3 | C | 32 |
4 | D | 6 |
5 | F | 7 |
6 | missing | 28 |
plt.grid()
hist(filter(x -> !ismissing(x), df1.oral_exam), bins=[ -0.49 + j for j in 0:61]);
plt.grid()
hist(filter(x -> !ismissing(x), df2.oral_exam), bins=[ -0.49 + j for j in 0:58]);
plt.grid()
hist(filter(x -> !ismissing(x), df1.exam_test), bins=[ -0.49 + j for j in 0:21]);
plt.grid()
hist(filter(x -> !ismissing(x), df2.exam_test), bins=[ -0.49 + j for j in 0:21]);
combine(groupby(df1, :exam_test), nrow => "počet") |> sort
13 rows × 2 columns
exam_test | počet | |
---|---|---|
Int64? | Int64 | |
1 | 0 | 4 |
2 | 10 | 1 |
3 | 11 | 1 |
4 | 12 | 3 |
5 | 13 | 2 |
6 | 14 | 5 |
7 | 15 | 29 |
8 | 16 | 37 |
9 | 17 | 40 |
10 | 18 | 37 |
11 | 19 | 34 |
12 | 20 | 16 |
13 | missing | 319 |
combine(groupby(df1[df1.assessment .=== true, :], :exam_test), nrow => "počet") |> sort
12 rows × 2 columns
exam_test | počet | |
---|---|---|
Int64? | Int64 | |
1 | 0 | 4 |
2 | 10 | 1 |
3 | 11 | 1 |
4 | 12 | 3 |
5 | 13 | 2 |
6 | 14 | 5 |
7 | 15 | 29 |
8 | 16 | 37 |
9 | 17 | 40 |
10 | 18 | 37 |
11 | 19 | 34 |
12 | 20 | 16 |
Reference
Další informace, detaily či tutoriály lze nalézt v dokumentaci DataFrames.jl
.
V notebooku jsme používali balíček CSV.jl
.