Juliaで遊んでみた - データフレーム操作編

最近流行りつつある科学計算処理向けの高水準言語「Julia」の基本操作をまとめていきます。
1.[準備編](https://qiita.com/HiroyukiTachikawa/items/f4c7a620987a45b0429b)
2.[確率分布・仮説検定と可視化編](https://qiita.com/HiroyukiTachikawa/items/dfc1b0e92d80287ea7f6)
3.[データフレーム操作編](https://qiita.com/HiroyukiTachikawa/items/e01917ade931031ec6a1)

## chapter.5 データフレーム操作
DataFrames.jl を使ったデータフレーム操作についてまとめます
公式：https://juliadata.github.io/DataFrames.jl/stable/man/getting_started/

### 1.パッケージ install
```julia
julia> import Pkg; Pkg.add("DataFrames")
```

### 2.データフレーム生成方法
行列データから生成する場合

```julia
julia> data = [
        1  0.179324    "A";
        2  0.818923    "B";
        3  0.979487    "C";
        4  0.882494    "A";
        5  0.00530208  "B"];

julia> DataFrame(data)
5×3 DataFrame
│ Row │ x1  │ x2         │ x3  │
│     │ Any │ Any        │ Any │
├─────┼─────┼────────────┼─────┤
│ 1   │ 1   │ 0.179324   │ A   │
│ 2   │ 2   │ 0.818923   │ B   │
│ 3   │ 3   │ 0.979487   │ C   │
│ 4   │ 4   │ 0.882494   │ A   │
│ 5   │ 5   │ 0.00530208 │ B   │
```

列名を与える場合

```julia
julia> DataFrame(data, [:A, :B, :C])
5×3 DataFrame
│ Row │ A   │ B          │ C   │
│     │ Any │ Any        │ Any │
├─────┼─────┼────────────┼─────┤
│ 1   │ 1   │ 0.179324   │ A   │
│ 2   │ 2   │ 0.818923   │ B   │
│ 3   │ 3   │ 0.979487   │ C   │
│ 4   │ 4   │ 0.882494   │ A   │
│ 5   │ 5   │ 0.00530208 │ B   │
```
列ベクトルから生成する場合

```julia
julia> DataFrame(
           A=1:5,
           B=rand(5),
           C=["A", "B", "C", "A", "B"])
5×3 DataFrame
│ Row │ A     │ B         │ C      │
│     │ Int64 │ Float64   │ String │
├─────┼───────┼───────────┼────────┤
│ 1   │ 1     │ 0.0974522 │ A      │
│ 2   │ 2     │ 0.194185  │ B      │
│ 3   │ 3     │ 0.280582  │ C      │
│ 4   │ 4     │ 0.838387  │ A      │
│ 5   │ 5     │ 0.745305  │ B      │
```

辞書型データから生成する場合
※以後このデータを使います

```julia
julia> data = Dict(
           :A => 1:5,
           :B => rand(5),
           :C => ["A", "B", "C", "A", "B"]);
julia> df = DataFrame(data)
5×3 DataFrame
│ Row │ A     │ B        │ C      │
│     │ Int64 │ Float64  │ String │
├─────┼───────┼──────────┼────────┤
│ 1   │ 1     │ 0.558038 │ A      │
│ 2   │ 2     │ 0.319128 │ B      │
│ 3   │ 3     │ 0.858239 │ C      │
│ 4   │ 4     │ 0.578579 │ A      │
│ 5   │ 5     │ 0.142839 │ B      │
```

#### ※symbol 型について補足
:A といった表記は symbol 型のデータを表しています。
筆者も詳しくありませんが、変数を表すためのデータと表現すると感覚的に近い気がしています。

```julia
julia> string = "A";
julia> typeof(string)
String

julia> symbol = :A;
julia> typeof(symbol)
Symbol

julia> Symbol(string) # String から Symbol への変換
:A
```

CSV から読み込む場合パッケージが必要

```julia

julia> import Pkg; Pkg.add("CSV")
julia> using CSV
```
手元にデータがないのでとりあえず書き込みから

```julia
julia> data = Dict(
           :A => 1:5,
           :B => rand(5),
           :C => ["A", "B", "C", "A", "B"]);
julia> df = DataFrame(data);
julia> CSV.write("dataframe.csv", df)
"dataframe.csv"
```
読み込み

```julia
julia> new_df = CSV.read("dataframe.csv");
julia> new_df
5×3 DataFrame
│ Row │ A     │ B         │ C      │
│     │ Int64 │ Float64   │ String │
├─────┼───────┼───────────┼────────┤
│ 1   │ 1     │ 0.0387064 │ A      │
│ 2   │ 2     │ 0.0974289 │ B      │
│ 3   │ 3     │ 0.281637  │ C      │
│ 4   │ 4     │ 0.566432  │ A      │
│ 5   │ 5     │ 0.685344  │ B      │
```

### 3.データ基本情報の確認

先頭 n 行を確認

```julia
julia>　n = 3;
julia> first(df, n)
3×3 DataFrame
│ Row │ A     │ B        │ C      │
│     │ Int64 │ Float64  │ String │
├─────┼───────┼──────────┼────────┤
│ 1   │ 1     │ 0.558038 │ A      │
│ 2   │ 2     │ 0.319128 │ B      │
│ 3   │ 3     │ 0.858239 │ C      │
```

最終 n 行を確認

```julia
julia>　n = 3;
julia> last(df, n)
3×3 DataFrame
│ Row │ A     │ B        │ C      │
│     │ Int64 │ Float64  │ String │
├─────┼───────┼──────────┼────────┤
│ 1   │ 3     │ 0.858239 │ C      │
│ 2   │ 4     │ 0.578579 │ A      │
│ 3   │ 5     │ 0.142839 │ B      │
```

行数と列数の確認

```julia
julia> size(df)
(5, 3)
```
カラム名を確認

```julia
julia> names(df)
3-element Array{Symbol,1}:
 :A
 :B
 :C
```

データ型を確認

```julia
julia> eltype.(eachcol(df))
3-element Array{DataType,1}:
 Int64  
 Float64
 String 
```

各種統計量（など）を確認

```julia
julia> describe(df)
3×8 DataFrame. Omitted printing of 2 columns
│ Row │ variable │ mean     │ min      │ median   │ max      │ nunique │
│     │ Symbol   │ Union…   │ Any      │ Union…   │ Any      │ Union…  │
├─────┼──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤
│ 1   │ A        │ 3.0      │ 1        │ 3.0      │ 5        │         │
│ 2   │ B        │ 0.491365 │ 0.142839 │ 0.558038 │ 0.858239 │         │
│ 3   │ C        │          │ A        │          │ C        │ 3       │
```
### 4.データ抽出
R, numpy, pandas とほぼ同じ書き方です

列を取り出す

```julia
julia> df.A
5-element Array{Int64,1}:
 1
 2
 3
 4
 5
```

複数列を取り出す

```julia
julia> df[:,[:A, :B]]
5×2 DataFrame
│ Row │ A     │ B        │
│     │ Int64 │ Float64  │
├─────┼───────┼──────────┤
│ 1   │ 1     │ 0.558038 │
│ 2   │ 2     │ 0.319128 │
│ 3   │ 3     │ 0.858239 │
│ 4   │ 4     │ 0.578579 │
│ 5   │ 5     │ 0.142839 │
```

列を取り出して算術も簡単

```julia

julia> df.D = df.A .* 40;
julia> df
5×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 1     │ 0.558038 │ A      │ 40    │
│ 2   │ 2     │ 0.319128 │ B      │ 80    │
│ 3   │ 3     │ 0.858239 │ C      │ 120   │
│ 4   │ 4     │ 0.578579 │ A      │ 160   │
│ 5   │ 5     │ 0.142839 │ B      │ 200   │
```
行を抽出

```julia
julia> df[2, :]
DataFrameRow
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 2   │ 2     │ 0.319128 │ B      │ 80    │
```
複数行を抽出

```julia

julia> df[2:5,:] # row index が変わっている点に注意!
4×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 2     │ 0.319128 │ B      │ 80    │
│ 2   │ 3     │ 0.858239 │ C      │ 120   │
│ 3   │ 4     │ 0.578579 │ A      │ 160   │
│ 4   │ 5     │ 0.142839 │ B      │ 200   │
```

条件抽出

```julia
julia> df[df.A.>3,:]
2×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 4     │ 0.578579 │ A      │ 160   │
│ 2   │ 5     │ 0.142839 │ B      │ 200   │

```
複数条件による抽出

```julia
julia> df[(df.A.>3).&(df.C.=="B"),:]
1×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 5     │ 0.142839 │ B      │ 200   │
```

抽出した条件の値を書き換える

```julia
julia> df[(df.A.>3).&(df.C.=="B"),:C] .= "X";
julia> df
5×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 1     │ 0.558038 │ A      │ 40    │
│ 2   │ 2     │ 0.319128 │ B      │ 80    │
│ 3   │ 3     │ 0.858239 │ C      │ 120   │
│ 4   │ 4     │ 0.578579 │ A      │ 160   │
│ 5   │ 5     │ 0.142839 │ X      │ 200   │
```
### 5.結合(join, append)
Union

```julia
julia> df2 = DataFrame(
            A=[10],
            B=[0.1],
            C=["C"],
            D=[20]);
julia> append!(df, df2) # 再帰的なので注意！
6×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 1     │ 0.558038 │ A      │ 40    │
│ 2   │ 2     │ 0.319128 │ B      │ 80    │
│ 3   │ 3     │ 0.858239 │ C      │ 120   │
│ 4   │ 4     │ 0.578579 │ A      │ 160   │
│ 5   │ 5     │ 0.142839 │ X      │ 200   │
│ 6   │ 10    │ 0.1      │ C      │ 20    │
```
Join
デフォルトでは inner join

```julia
julia> df3 = DataFrame(C = ["A", "B"],E=["abc", "xyz"]);
julia> join(df, df3, on=:C)
3×5 DataFrame
│ Row │ A     │ B        │ C      │ D     │ E      │
│     │ Int64 │ Float64  │ String │ Int64 │ String │
├─────┼───────┼──────────┼────────┼───────┼────────┤
│ 1   │ 1     │ 0.558038 │ A      │ 40    │ abc    │
│ 2   │ 2     │ 0.319128 │ B      │ 80    │ xyz    │
│ 3   │ 4     │ 0.578579 │ A      │ 160   │ abc    │
```
left join

```julia
julia> join(df, df3, on=:C, kind=:left)
6×5 DataFrame
│ Row │ A     │ B        │ C      │ D     │ E       │
│     │ Int64 │ Float64  │ String │ Int64 │ String⍰ │
├─────┼───────┼──────────┼────────┼───────┼─────────┤
│ 1   │ 1     │ 0.558038 │ A      │ 40    │ abc     │
│ 2   │ 2     │ 0.319128 │ B      │ 80    │ xyz     │
│ 3   │ 3     │ 0.858239 │ C      │ 120   │ missing │
│ 4   │ 4     │ 0.578579 │ A      │ 160   │ abc     │
│ 5   │ 5     │ 0.142839 │ X      │ 200   │ missing │
│ 6   │ 10    │ 0.1      │ C      │ 20    │ missing │
#  なぜか文字化けが起こる...
```
詳細は[こちら](https://juliadata.github.io/DataFrames.jl/stable/man/joins/)

### 6.集計(aggregate, by)
aggregate 関数
・第一引数にdataframe
・第二引数に集計 key とするカラム名（配列にすると multi index 集計）
・第三引数に集計方法（配列可）

```julia
julia> aggregate(df, :C, sum) 
4×4 DataFrame
│ Row │ C      │ A_sum │ B_sum    │ D_sum │
│     │ String │ Int64 │ Float64  │ Int64 │
├─────┼────────┼───────┼──────────┼───────┤
│ 1   │ A      │ 5     │ 1.13662  │ 200   │
│ 2   │ B      │ 2     │ 0.319128 │ 80    │
│ 3   │ C      │ 13    │ 0.958239 │ 140   │
│ 4   │ X      │ 5     │ 0.142839 │ 200   │

```
※集計を行う場合は Statistics.jl を入れておいたほうがいい。

```juila
julia> import Pkg; Pkg.add("Statistics")
julia> using Statistics
```
なぜかmeanも無い（ describe では mean あったのに何故!?）

by 関数
・第一引数にdataframe
・第二引数に集計 key とするカラム名（配列にすると multi index 集計）
・第三引数に辞書型で集計対象（key）と集計方法(value)を指定


例1.

```julia
julia> by(df, :C ,:A=>sum)
4×2 DataFrame
│ Row │ C      │ A_sum │
│     │ String │ Int64 │
├─────┼────────┼───────┤
│ 1   │ A      │ 5     │
│ 2   │ B      │ 2     │
│ 3   │ C      │ 13    │
│ 4   │ X      │ 5     │
```

例2.

```julia
julia> by(df, :C ,(:A=>sum, :A=>mean, :B=>std))
4×4 DataFrame
│ Row │ C      │ A_sum │ A_mean  │ B_std    │
│     │ String │ Int64 │ Float64 │ Float64  │
├─────┼────────┼───────┼─────────┼──────────┤
│ 1   │ A      │ 5     │ 2.5     │ 0.014525 │
│ 2   │ B      │ 2     │ 2.0     │ NaN      │
│ 3   │ C      │ 13    │ 6.5     │ 0.536156 │
│ 4   │ X      │ 5     │ 5.0     │ NaN      │
```
### 7.その他
sort 関数
第二引数で指定したカラム名でソート

```julia
julia> sort!(df, :B)
6×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 10    │ 0.1      │ C      │ 20    │
│ 2   │ 5     │ 0.142839 │ X      │ 200   │
│ 3   │ 2     │ 0.319128 │ B      │ 80    │
│ 4   │ 1     │ 0.558038 │ A      │ 40    │
│ 5   │ 4     │ 0.578579 │ A      │ 160   │
│ 6   │ 3     │ 0.858239 │ C      │ 120   │
```
降順

```julia
julia> sort!(df, :B, rev = true)
6×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 3     │ 0.858239 │ C      │ 120   │
│ 2   │ 4     │ 0.578579 │ A      │ 160   │
│ 3   │ 1     │ 0.558038 │ A      │ 40    │
│ 4   │ 2     │ 0.319128 │ B      │ 80    │
│ 5   │ 5     │ 0.142839 │ X      │ 200   │
│ 6   │ 10    │ 0.1      │ C      │ 20    │
```
カラム名の変更
rename 関数
例1. リストで変更

```julia
julia> new_colname = [:W, :X, :Y, :Z];
julia> rename!(df, new_colname)
6×4 DataFrame
│ Row │ W     │ X        │ Y      │ Z     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 3     │ 0.858239 │ C      │ 120   │
│ 2   │ 4     │ 0.578579 │ A      │ 160   │
│ 3   │ 1     │ 0.558038 │ A      │ 40    │
│ 4   │ 2     │ 0.319128 │ B      │ 80    │
│ 5   │ 5     │ 0.142839 │ X      │ 200   │
│ 6   │ 10    │ 0.1      │ C      │ 20    │
```
例2. 辞書で変更

```julia
julia> new_colname = Dict(:W=>:A, :X =>:B, :Y=>:C, :Z=>:D);
julia> rename!(df, new_colname)
6×4 DataFrame
│ Row │ A     │ B        │ C      │ D     │
│     │ Int64 │ Float64  │ String │ Int64 │
├─────┼───────┼──────────┼────────┼───────┤
│ 1   │ 3     │ 0.858239 │ C      │ 120   │
│ 2   │ 4     │ 0.578579 │ A      │ 160   │
│ 3   │ 1     │ 0.558038 │ A      │ 40    │
│ 4   │ 2     │ 0.319128 │ B      │ 80    │
│ 5   │ 5     │ 0.142839 │ X      │ 200   │
│ 6   │ 10    │ 0.1      │ C      │ 20    │
```