Julia で統計解析 第 3 章 データフレームの取り扱い¶

In [1]:
Version()
最新バージョン 2024-10-29 13:47

データを使用するための準備¶

Julia では,データセットはデータフレームとして入力・作成し利用する。

この章では,Julia で引用できるデータや簡単なデータフレームを例として,データフレームの操作法について述べる。

既存のデータを使用する¶

別途,R がインストールされているのが前提であるが,以下のようにして R で用意されているデータセットを Julia で利用することができる。

airquality データセットは,1973 年の 5 月から 9 月の間の日々のオゾン濃度,日射量,風速,気温の記録である。

iris データセットは,アヤメ科の 3 種,それぞれ 50 個の花の 4 個の測定値と種名のデータである。

なお,R におけるデータフレームの列名と違う場合があるので注意が必要である。 たとえば,Solar.R という列名が Solar_R という列名であったり,Sepal.Length という列名が SepalLength という列目であったりするので,読み込んだ後で確認するほうがよい。

RDatasets パッケージの dataset() を使う場合¶

using RDatasets データフレーム名 = dataset("datasets", "データセット名");

何らかの事情でこの方法が使えない場合(macOS の Julia の場合)は,次の方法を試してみてほしい。

In [2]:
using RDatasets
airquality = dataset("datasets", "airquality");
first(airquality, 3) # 最初の 3 行を示す
Out[2]:
3×6 DataFrame
RowOzoneSolar.RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453
In [3]:
using RDatasets
iris = dataset("datasets", "iris");
first(iris, 3) # 最初の 3 行を示す
Out[3]:
3×5 DataFrame
RowSepalLengthSepalWidthPetalLengthPetalWidthSpecies
Float64Float64Float64Float64Cat…
15.13.51.40.2setosa
24.93.01.40.2setosa
34.73.21.30.2setosa

RCall パッケージの rcopy() を使う場合¶

using RCall データフレーム名 = rcopy(R"データセット名")

In [4]:
using RCall
airquality = rcopy(R"airquality")
first(airquality, 3) # 最初の 3 行を示す
Out[4]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453
In [5]:
using RCall
iris = rcopy(R"iris")
first(iris, 3) # 最初の 3 行を示す
Out[5]:
3×5 DataFrame
RowSepal_LengthSepal_WidthPetal_LengthPetal_WidthSpecies
Float64Float64Float64Float64Cat…
15.13.51.40.2setosa
24.93.01.40.2setosa
34.73.21.30.2setosa

自前のデータ¶

データフレームの入出力については「Julia で統計解析--その2 データの取扱」を参照のこと。

まず最初に,DataFrames パッケージの使用する場合,以下の 1 行が必要である。

In [6]:
using DataFrames

DataFrame() に 変数名(列名)= データベクトル の形式で列挙すれば,簡単なデータフレームを作ることができる。

Julia におけるデータベクトルは,要素をカンマで区切り列挙したものを [ ] で囲んだものである。要素は,整数,実数,文字列,文字などである。

以下の例示で,ベクトル |> println としたのは,REPL 環境では単に ベクトル だけを入力すると複数行の出力になるのを防ぐためである。println(ベクトル) のように書けば同じ効果が得られるが,表示したいものを強調するために |> println を使う。

In [7]:
[2, 4, 3, 6, 1] # 数値ベクトル
Out[7]:
5-element Vector{Int64}:
 2
 4
 3
 6
 1
In [8]:
[2, 4, 3, 6, 1] |> println # 数値ベクトル
[2, 4, 3, 6, 1]
In [9]:
println([2, 4, 3, 6, 1]) # 数値ベクトル
[2, 4, 3, 6, 1]
In [10]:
["foo", "bar", "baz", "boo", "vax"] |> println # 文字列ベクトル
["foo", "bar", "baz", "boo", "vax"]
In [11]:
1:5 |> println # 等差数列
1:5
In [12]:
collect(1:5) |> println # 等差数列 1:5 とほとんど同じ
[1, 2, 3, 4, 5]
In [13]:
'a':'e' |> println # 文字ベクトル
'a':1:'e'
In [14]:
collect('a':'e') |> println # 文字ベクトル 'a':'e' とほとんど同じ
['a', 'b', 'c', 'd', 'e']
In [15]:
rand(3) |> println # [0, 1) の一様乱数
[0.1685273947698881, 0.08239455213768898, 0.2330147400788538]
In [16]:
rand(1:100, 3) |> println # 1:100 から無作為抽出
[51, 3, 58]
In [17]:
randn(3) |> println # μ=0, σ=1 の標準正規乱数
[0.10522287393415958, 0.45968886484651456, 1.5449057305845453]
In [18]:
randn(3) .* 10 .+ 50 |> println # μ=50, σ=10 の規乱数
[55.25276330519599, 51.69563962013977, 37.14137856837596]
In [19]:
df = DataFrame(a = [2, 4, 3, 6, 1], b = ["foo", "bar", "baz", "boo", "vax"])
Out[19]:
5×2 DataFrame
Rowab
Int64String
12foo
24bar
33baz
46boo
51vax
In [20]:
df |> println
5×2 DataFrame
 Row │ a      b      
     │ Int64  String 
─────┼───────────────
   1 │     2  foo
   2 │     4  bar
   3 │     3  baz
   4 │     6  boo
   5 │     1  vax

データフレームの入出力については

Julia で統計解析--その2 データの取扱 を参照のこと。

データフレームの概要¶

データフレームの大きさ¶

データセットの大きさは size() で得られる。結果はタプル (行数, 列数) で返される。

In [21]:
size(airquality) |> println
(153, 6)

データフレームの行数は nrow() で得られる。

In [22]:
nrow(airquality) |> println
153

データフレームの列数は ncol() で得られる。

In [23]:
ncol(airquality) |> println
6

データフレームの変数名¶

変数名は names() で得られる。

In [24]:
names(airquality) |> println
["Ozone", "Solar_R", "Wind", "Temp", "Month", "Day"]
In [25]:
names(iris) |> println
["Sepal_Length", "Sepal_Width", "Petal_Length", "Petal_Width", "Species"]

シンボリック名は propertynames() で得られる。

In [26]:
propertynames(airquality) |> println
[:Ozone, :Solar_R, :Wind, :Temp, :Month, :Day]
In [27]:
propertynames(iris) |> println
[:Sepal_Length, :Sepal_Width, :Petal_Length, :Petal_Width, :Species]

データフレームの表示¶

データフレームの最初の $n$ 行,最後の $m$ 行を表示するには,first(df, n), last(df, m) のように指定する。 n, m が省略された場合は 1 が仮定される。

In [28]:
first(airquality, 6)
Out[28]:
6×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453
41831311.56254
5missingmissing14.35655
628missing14.96656
In [29]:
last(iris, 3)
Out[29]:
3×5 DataFrame
RowSepal_LengthSepal_WidthPetal_LengthPetal_WidthSpecies
Float64Float64Float64Float64Cat…
16.53.05.22.0virginica
26.23.45.42.3virginica
35.93.05.11.8virginica

データフレームの表示の 1 行目は,列名(変数名),2 行目はそれぞれのデータの型である。 Int64 は整数型,Float64 は実数型,String,String3,String7 などは文字列型である。なお,それぞれの型名の後に ? がついている場合は,データ中に欠損値 missing が含まれることを表している。

データフレームのコピーは copy() で¶

データフレーム df1 のコピーを df2 = df1 で作ったつもりになって,コピーされたデータフレーム df2 を書き換えると df1 も同じように書き換わってしまう。

代入してできた df2 は実は df1 の言ってみれば別名なのだ。df1 を変更しても,df2 を変更しても,もう一方のデータフレームも同じように変更されているように見える。

In [30]:
df1 = DataFrame(a = [1, 2], b = ['a', 'b'])
Out[30]:
2×2 DataFrame
Rowab
Int64Char
11a
22b
In [31]:
df2 = df1
df2[1, 1] = 99999
df2
Out[31]:
2×2 DataFrame
Rowab
Int64Char
199999a
22b
In [32]:
df1
Out[32]:
2×2 DataFrame
Rowab
Int64Char
199999a
22b
In [33]:
df2 === df1 # df2 と df1 はコンピュータのメモリ上で同じ(`===`)ものか? ---> true
Out[33]:
true

df2 = copy(df1) とすれば,df1 と df2 は別物になり,このようなことにはならない。

In [34]:
df1 = DataFrame(a = [1, 2], b = ['a', 'b'])
df2 = copy(df1)
df2[1, 1] = 99999
df2
Out[34]:
2×2 DataFrame
Rowab
Int64Char
199999a
22b
In [35]:
df1
Out[35]:
2×2 DataFrame
Rowab
Int64Char
11a
22b
In [36]:
df2 === df1 # df2 と df1 は同じものか? ---> false 別物
Out[36]:
false

空データフレーム¶

指定されたデータフレームと同じ列名と同じ型を持つ 0 行のデータフレームを作る。

In [37]:
empty(airquality)
Out[37]:
0×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64

empty!(df) により,df の列名だけを残し,すべての行を削除する。意図しないなら危険!注意!

In [38]:
df1 = DataFrame(a = [1, 2], b = ['a', 'b'])
empty!(df1)
df1
Out[38]:
0×2 DataFrame
Rowab
Int64Char

データフレームの列の参照¶

Julia では,列を参照するときに何通りかの方法がある。

以下の方法は,1 つの列全体を参照する。 列名の前に : を付けたもの,以下の例では :Ozone は Julia では「シンボル」と呼ばれる(必ずしもデータフレームの取り扱いのときだけでなく,整数や文字列のようなものとして扱われる)。

[:, 列] のような指定において,: はすべての行を表す。

In [39]:
airquality.Ozone;
airquality."Ozone"
airquality[:, :Ozone];
airquality[:, "Ozone"];
firstcolumn = :Ozone
airquality[:, firstcolumn];
airquality[:, 1];

1 番目の指定法が一番わかり易いと思うが,例からも想像できるように,変数名の中に . があるとエラーになる。そのような場合には前もって列名を変更する(「1.3. データフレームの列名の変更」参照)か 2 番目の方法すなわち引用符でくくればよい。

行や列を範囲で参照するときには [行範囲, 列範囲] のように指定する。

範囲の指定は,数1:数2のように表したときは数1から数2までの整数,[数1, 数2, …] あるいは [シンボル1, シンボル2, …] あるいは [文字列1, 文字列2, …] のように表した場合はそれぞれをまとめたもの(集合)として扱われる。特に,: が使われる場合はその行や列全部を表す。

なお,文字列での指定ととシンボルでの指定は混ぜて使用することはできない。

In [40]:
first(airquality[:, [1, 3]], 5) # 1,3列のすべての行
Out[40]:
5×2 DataFrame
RowOzoneWind
Int64?Float64
1417.4
2368.0
31212.6
41811.5
5missing14.3
In [41]:
first(airquality[:, ["Solar_R", "Month"]], 5) # "Solar.R" と "Month" のすべての行
Out[41]:
5×2 DataFrame
RowSolar_RMonth
Int64?Int64
11905
21185
31495
43135
5missing5

データフレームの行の参照¶

データフレームを [a, b] で指定する場合,第 1 引数は行,第 2 引数は列を指定する。

In [42]:
airquality[1:5, [1, 3]] # 1〜5 行の 1, 3 列
Out[42]:
5×2 DataFrame
RowOzoneWind
Int64?Float64
1417.4
2368.0
31212.6
41811.5
5missing14.3

第 1 引数は : で表す他に ! で表すこともある。: はデータフレームをコピーして参照するが,! は直接参照する。

下の 2 つの例において,test の第 1 列は元のままの Int64? であるが,test2 の第 1 列は,test2 の第 2 列と同じ Float64 になる。 この違いは,大したことではないこともあるが,大きな落とし穴になることもあるので注意が必要である。

In [43]:
test = airquality[1:5, [1, 3]]
test[:, 1] = test[:, 2] .* 10
test
Out[43]:
5×2 DataFrame
RowOzoneWind
Int64?Float64
1747.4
2808.0
312612.6
411511.5
514314.3
In [44]:
test2 = airquality[1:5, [1, 3]]
test2[!, 1] = test2[:, 2] .* 5
test2
Out[44]:
5×2 DataFrame
RowOzoneWind
Float64Float64
137.07.4
240.08.0
363.012.6
457.511.5
571.514.3

たとえば,下の例では test3[:, 1] は InexactError: Int64(57.5) というエラーを発生する。つまり,「元のままの Int64? に 57.5 の整数部 Int(57.5) を代入しているが精度が失われるよ」というエラーである。

大雑把な方針としては,左辺のデータフレームには ! を使うほうが自然かもしれない。

In [45]:
test3 = airquality[1:5, [1, 3]]
test3[:, 1] = test3[:, 2] .* 5
test3
InexactError: Int64(57.5)

Stacktrace:
  [1] Int64
    @ ./float.jl:994 [inlined]
  [2] convert
    @ ./number.jl:7 [inlined]
  [3] convert
    @ ./missing.jl:70 [inlined]
  [4] setindex!
    @ ./array.jl:976 [inlined]
  [5] macro expansion
    @ ./multidimensional.jl:981 [inlined]
  [6] macro expansion
    @ ./cartesian.jl:64 [inlined]
  [7] _unsafe_setindex!(::IndexLinear, A::Vector{Union{Missing, Int64}}, x::Vector{Float64}, I::Base.Slice{Base.OneTo{Int64}})
    @ Base ./multidimensional.jl:979
  [8] _setindex!
    @ ./multidimensional.jl:967 [inlined]
  [9] setindex!(A::Vector{Union{Missing, Int64}}, v::Vector{Float64}, I::Function)
    @ Base ./abstractarray.jl:1413
 [10] setindex!(df::DataFrame, v::Vector{Float64}, row_inds::Colon, col_ind::Int64)
    @ DataFrames ~/.julia/packages/DataFrames/kcA9R/src/dataframe/dataframe.jl:731
 [11] top-level scope
    @ In[45]:2

データフレームの列名の変更¶

列名の変更は rename!() による。 Julia の一般的な規約であるが,関数名の最後が ! になっている場合は,関数への引数がインプレースで変更されるので代入の必要はない。 逆に言えば,元の引数が変更されてしまうので,前もってコピーをとっておかない限りもとに戻せなくなるので注意が必要である。

第 2 引数以降に,元の名前 => 新しい名前 の形式で列挙する。

In [46]:
airquality2 = rename(airquality, Dict("Solar_R" => "SolarR"));
first(airquality2)
Out[46]:
DataFrameRow (6 columns)
RowOzoneSolarRWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751

rename() の場合は,引数として指定した元のデータフレームは変更されない。したがって,もし変更結果を保存したい場合はデータフレーム(自分自身でもよい)に保存しなければならない

In [47]:
first(airquality)
Out[47]:
DataFrameRow (6 columns)
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751

rename!() では,引数として指定した元のデータフレームが変更される。

In [48]:
rename!(airquality, Dict("Solar_R" => "SolarR"));
first(airquality)
Out[48]:
DataFrameRow (6 columns)
RowOzoneSolarRWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
In [49]:
rename!(airquality, Dict("SolarR" => "Solar_R"));
first(airquality)
Out[49]:
DataFrameRow (6 columns)
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751

列の抽出¶

select(),select!() は列の抽出を行う。抽出した後の順序は指定したとおりになる(単純に並べ替えるだけということも可)。 列の指定は何通りかある。

In [50]:
df = select(airquality, [4, 3, 2, 1]); # 列番号で指定
first(df)
Out[50]:
DataFrameRow (4 columns)
RowTempWindSolar_ROzone
Int64Float64Int64?Int64?
1677.419041
In [51]:
df = select(airquality, [:Wind, :Temp, :Ozone, :Solar_R]); # シンボルで指定
first(df)
Out[51]:
DataFrameRow (4 columns)
RowWindTempOzoneSolar_R
Float64Int64Int64?Int64?
17.46741190

列を(名前を変えて)コピーしたり,変数変換することもできる。ByRow() はすべての行に対して,引数で指定する関数または無名関数を適用する。

たとえば,元の Temp を華氏温度 Fahrenheit に名前を変えて,Temp を変換して摂氏温度 Celcius を作成する。

x -> 5(x-32)/9 が無名関数で,引数を x として 5(x-32)/9 を計算する。

関数で定義するとすれば func(x) = 5(x-32)/9 と同じである。

In [52]:
df = select(airquality, :Ozone, :Solar_R, :Wind,
        :Temp => :Fahrenheit, :Temp => ByRow(x -> 5(x-32)/9) => :Celcius);
first(df, 5)
Out[52]:
5×5 DataFrame
RowOzoneSolar_RWindFahrenheitCelcius
Int64?Int64?Float64Int64Float64
1411907.46719.4444
2361188.07222.2222
31214912.67423.3333
41831311.56216.6667
5missingmissing14.35613.3333

指定された列を削除する¶

削除は,「select しない」ということと同じである。Not(列名ベクトル) で指定できる。

:Ozone を除く。

In [53]:
select(airquality, Not(:Ozone))[1:3, :] # 最初の 3 行だけ示す
Out[53]:
3×5 DataFrame
RowSolar_RWindTempMonthDay
Int64?Float64Int64Int64Int64
11907.46751
21188.07252
314912.67453

:Month と :Day を除く。

In [54]:
select(airquality, Not([:Month, :Day]))[1:3, :] # 最初の 3 行だけ示す
Out[54]:
3×4 DataFrame
RowOzoneSolar_RWindTemp
Int64?Int64?Float64Int64
1411907.467
2361188.072
31214912.674

行の抽出¶

行を指定するときに,任意のベクトルを書けば,その要素に対応する行が抽出される。

In [55]:
airquality[1:3, :] # 1 〜 3 行目を抽出
Out[55]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453
In [56]:
airquality[1:20:end, :] # 1行目 から 20 行おきに抽出
Out[56]:
8×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2189.759521
33932311.587610
4missing1388.083630
56322011.585720
61102078.09089
71182252.394829
8132710.376918
In [57]:
airquality[vcat(1:2, 5...), :] # 1, 2, 5 行を抽出
Out[57]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2361188.07252
3missingmissing14.35655

行を表す位置に論理式を書くことができる。この場合には,条件を満たす行が抽出される。

In [58]:
airquality[airquality.Wind .> 20, :] # Wind が 20 以上の行を抽出する .> はベクトル演算ゆえ . が付く
Out[58]:
2×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
181920.16159
23728420.772617

これを次のように書くこともできる。

row -> row.Wind > 20 は無名関数である。row を引数として row.Wind > 20 という条件式を書いている。

In [59]:
filter(row -> row.Wind > 20, airquality) # .> ではなく > である
Out[59]:
2×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
181920.16159
23728420.772617

以下の例では,Wind > 15 かつ Temp <= 65 の行を抽出する。

In [60]:
airquality[(airquality.Wind .> 15) .&& (airquality.Temp .<= 65), :]
Out[60]:
4×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
181920.16159
267818.457518
3missing6616.657525
4142016.663925

上の例では,airquality を 3 回も書かなければならないが,下のように書くと 1 回で済む(ただし慣れないと読むのが難しい)。

In [61]:
filter([:Wind, :Temp] => (x, y) -> x > 15 && y <= 65, airquality)
Out[61]:
4×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
181920.16159
267818.457518
3missing6616.657525
4142016.663925

行の削除¶

「条件を満たさない行を削除する」ことは,「条件を満たす行を抽出する」ことと同じである。

In [62]:
df = DataFrame(a = 1:4, b = 'a':'d')
Out[62]:
4×2 DataFrame
Rowab
Int64Char
11a
22b
33c
44d

「a が 偶数」という条件を満たさない行を削除する,つまり,「a が 奇数」という行を削除するということは,「a が 偶数」という条件を満たす行を抽出することである。

In [63]:
df[iseven.(df.a), :]
Out[63]:
2×2 DataFrame
Rowab
Int64Char
12b
24d

重複を除き,ユニークな行のみを含むデータフレームを作る¶

unique()/unique!() を使う。unique!() はインプレースで作用する。

見た目が同じ行が削除されるのであって,本来別々の測定対象がたまたま全く同じ測定値になったような場合でも重複とみなされて削除されてしまう。 測定対象を識別するユニークなコードも含めて重複の有無を判定すべきである。

In [64]:
df = DataFrame(a = [1, 2, 1], b = ['a', 'b', 'a'])
Out[64]:
3×2 DataFrame
Rowab
Int64Char
11a
22b
31a
In [65]:
unique(df) # 3 行目は 1 行目と見た目が同じなので削除される
Out[65]:
2×2 DataFrame
Rowab
Int64Char
11a
22b

欠損値を含む行か含まない行か¶

欠損値を含まない行なら 1(true),含む行なら 0(false) の Bool 型のベクトルを返す。

In [66]:
df = DataFrame(a = [1, 2, 1], b = ['a', missing, 'c'])
Out[66]:
3×2 DataFrame
Rowab
Int64Char?
11a
22missing
31c
In [67]:
completecases(df) |> println
Bool[1, 0, 1]

欠損値を含まない行を抽出する¶

前節の completecases() と行の選択を併せて,欠損値を含まない行を抽出することができる。

In [68]:
df = DataFrame(a = [1,2,1], b = ['a', 'b', 'a'])
df[completecases(df), :]
Out[68]:
3×2 DataFrame
Rowab
Int64Char
11a
22b
31a
In [69]:
dropmissing(df) # および dropmissing!(df)
Out[69]:
3×2 DataFrame
Rowab
Int64Char
11a
22b
31a

データフレームの列方向連結¶

既存のデータフレームの右に別のデータフレームを連結するのは hcat() を使う。インプレイスでの連結ではないので,連結結果を後で利用したい場合は結果を代入する。

行数が異なるデータフレームを連結しようとするとエラーになる。Julia のデータフレームは行名を持たないので,連結しようとする 2 つのデータフレームの各行が同じ対象のものであることを保証するのはユーザの責任である。

特定の列名を基準としてデータフレームをマージする場合は hcat() ではなく innerjoin,leftjoin などを使う。

In [70]:
df1 = DataFrame(a = [2, 1, 4], b = ["ab", "cde", "fg"])
Out[70]:
3×2 DataFrame
Rowab
Int64String
12ab
21cde
34fg
In [71]:
df2 = DataFrame(c = [1.2, 3.4, 5.6])
Out[71]:
3×1 DataFrame
Rowc
Float64
11.2
23.4
35.6
In [72]:
df3 = hcat(df1, df2)
Out[72]:
3×3 DataFrame
Rowabc
Int64StringFloat64
12ab1.2
21cde3.4
34fg5.6

連結するデータフレームに同じ列名があるとエラーになるが,makeunique = true を指定しておくと,列名_番号 のように名前を変えて連結することができる。

In [73]:
df4 = hcat(df1, df2, df2, makeunique = true)
Out[73]:
3×4 DataFrame
Rowabcc_1
Int64StringFloat64Float64
12ab1.21.2
21cde3.43.4
34fg5.65.6

データフレームの行方向連結¶

連結しようとしている 2 つのデータフレーム df1, df2 に含まれる列名の集合を set1, set2 とする。

append!(df1, df2) により,df1 の後に df2 を連結する。

append!() はインプレースの連結なので,df1 は 結果で書き換えられる(結果を別のデータフレームに代入することもできる)。

vcat(df1, df2) も同じように,df1 の後に df2 を連結するが,vcat() はインプレースではないので,後で結果を利用したい場合は別のデータフレームに代入しなければならない。

append!() も vcat() も,第 3 引数 cols で連結する列に関する詳細な設定ができる。

cols = :setequal, cols=:orderequal¶

append!(df1, df2, cols=:setequal) で df1 の後に df2 を連結する。

cols=:setequal はデフォルトで,df1, df2 の列数と名前は同じでなければならないことを表す。順序は違ってもよい。

cols=:orderequal を指定すると,順序も同じでなければならないことを表す(順序が違えばエラーになる)。

以下の例は,df1 も df2 も 列名 a, b を持つ場合である。

In [74]:
df1 = DataFrame(a = 1:3, b = 'A':'C')
Out[74]:
3×2 DataFrame
Rowab
Int64Char
11A
22B
33C
In [75]:
df2 = DataFrame(b = ['X', 'Y'], a = 11:12)
Out[75]:
2×2 DataFrame
Rowba
CharInt64
1X11
2Y12
In [76]:
append!(df1, df2,);
df1
Out[76]:
5×2 DataFrame
Rowab
Int64Char
11A
22B
33C
411X
512Y

cols = :intersect¶

cols=:setequal の場合には列名の集合は同じでなければならないが,cols=:intersectの場合には df1 の列名の集合が df2 の列名の集合に包含されていればよい。df1 に含まれるすべての列名が df2 にも含まれていなければならない。

In [77]:
df1 = DataFrame(a = 1:3, b = 'a':'c')
Out[77]:
3×2 DataFrame
Rowab
Int64Char
11a
22b
33c
In [78]:
df2 = DataFrame(a = 11:12, b = ['X', 'Y'], c = [1.2, 3.4])
Out[78]:
2×3 DataFrame
Rowabc
Int64CharFloat64
111X1.2
212Y3.4
In [79]:
append!(df1, df2, cols = :intersect);
df1
Out[79]:
5×2 DataFrame
Rowab
Int64Char
11a
22b
33c
411X
512Y

cols = :subset¶

cols=:subset の場合には df1 と df2 に共通する列名のデータはそのまま連結されるが,df1 にはあるが df2 にはない列名のデータは missing になる。

In [80]:
df1 = DataFrame(a = 1:3, b = 'A':'C')
Out[80]:
3×2 DataFrame
Rowab
Int64Char
11A
22B
33C
In [81]:
df2 = DataFrame(a = 11:12, c = [1.2, 3.4])
Out[81]:
2×2 DataFrame
Rowac
Int64Float64
1111.2
2123.4
In [82]:
append!(df1, df2, cols = :subset);
df1
Out[82]:
5×2 DataFrame
Rowab
Int64Char?
11A
22B
33C
411missing
512missing

cols = :union¶

df1 と df2 のいずれかに含まれる列名を含むデータフレームとして連結される。

互いに含まれない列名のデータとしては missing が連結される。

In [83]:
df1 = DataFrame(a = 1:3, b = 'A':'C')
Out[83]:
3×2 DataFrame
Rowab
Int64Char
11A
22B
33C
In [84]:
df2 = DataFrame(a = 11:12, c = [1.2, 3.4])
Out[84]:
2×2 DataFrame
Rowac
Int64Float64
1111.2
2123.4
In [85]:
append!(df1, df2, cols = :union);
df1
Out[85]:
5×3 DataFrame
Rowabc
Int64Char?Float64?
11Amissing
22Bmissing
33Cmissing
411missing1.2
512missing3.4

データフレームの最終行の次に 1 行追加する¶

push!(df1, 追加行) のようにしてインプレースで追加する。

追加行は Dict() またはデータフレームの一行で指定する。前節の append!() と同じく,cols 引数が使える。

In [86]:
df1 = DataFrame(a = 1:3, b = 'A':'C')
Out[86]:
3×2 DataFrame
Rowab
Int64Char
11A
22B
33C
In [87]:
push!(df1, Dict(:a => 5, :b => 'X', :c => 1.23), cols = :union)
Out[87]:
4×3 DataFrame
Rowabc
Int64CharFloat64?
11Amissing
22Bmissing
33Cmissing
45X1.23
In [88]:
push!(df1, df1[1, :])
Out[88]:
5×3 DataFrame
Rowabc
Int64CharFloat64?
11Amissing
22Bmissing
33Cmissing
45X1.23
51Amissing

行を繰り返してデータフレームを作る¶

repeat() でデータの繰り返しを作る。

In [89]:
df = DataFrame(a = 1:2, b = 3:4)
Out[89]:
2×2 DataFrame
Rowab
Int64Int64
113
224
In [90]:
repeat(df, inner = 2, outer = 3)
Out[90]:
12×2 DataFrame
Rowab
Int64Int64
113
213
324
424
513
613
724
824
913
1013
1124
1224
In [91]:
df = DataFrame(a = 1:2, b = 3:4);
repeat(df, 2)
Out[91]:
4×2 DataFrame
Rowab
Int64Int64
113
224
313
424
In [92]:
df = DataFrame(a = 1:2, b = 3:4);
repeat!(df, inner = 2, outer = 3); # 破壊的
first(df, 10)
Out[92]:
10×2 DataFrame
Rowab
Int64Int64
113
213
324
424
513
613
724
824
913
1013
In [93]:
df = DataFrame(a = 1:2, b = 3:4);
repeat!(df, 2); # 破壊的
df
Out[93]:
4×2 DataFrame
Rowab
Int64Int64
113
224
313
424

データフレームに列を挿入する¶

insertcols!(df1, 挿入位置, 列名1 => データ1, …) により,df1 の挿入位置へ,順に列をインプレースで挿入する。複数の列を挿入できる。

In [94]:
df1 = DataFrame(a = 1:3, b = 'A':'C')
Out[94]:
3×2 DataFrame
Rowab
Int64Char
11A
22B
33C
In [95]:
insertcols!(df1, 2, :col2 => [1.2, 3.4, 5.67], :col3 => ["No1", "No2", "No3"])
Out[95]:
3×4 DataFrame
Rowacol2col3b
Int64Float64StringChar
111.2No1A
223.4No2B
335.67No3C

データフレームの要素から新しいデータフレームを作る¶

combine() は既存の列変数から新しいデータフレームを作る。

In [96]:
first(airquality, 3)
Out[96]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64?Int64?Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453

:Wind は mile/hour, :Temp は 華氏温度なので,それぞれ m/sec,摂氏温度に変換し,名前もつける。

x -> 0.4470389x,x -> 5(x.-32)/9 は無名関数

In [97]:
df = combine(airquality, :Wind => (x -> 0.4470389x) => :m_by_sec, "Temp" => (x -> 5(x.-32)/9) => :℃)
first(df, 3)
Out[97]:
3×2 DataFrame
Rowm_by_sec℃
Float64Float64
13.3080919.4444
23.5763122.2222
35.6326923.3333

データフレームの各列に関数を施す¶

mapcols()/mapcols!() の第 1 引数は関数または無名関数である。その関数をデータフレームの各列に適用する。

mapcols() は元のデータフレームは元のまままで。結果を利用したい場合はデータフレームに代入しなければならない。

mapcols!() はインプレースで列の変更が行われるので代入は不要であるが,データフレームを書き換えてはいけない場合には注意が必要である。

In [98]:
df = DataFrame(x = 1:4, y = 11:14)
Out[98]:
4×2 DataFrame
Rowxy
Int64Int64
1111
2212
3313
4414
In [99]:
mapcols(a -> a.^2, df) # df は元のまま。利用したければデータフレームに代入する
df
Out[99]:
4×2 DataFrame
Rowxy
Int64Int64
1111
2212
3313
4414
In [100]:
df2 = mapcols(a -> a.^2, df) # df は元のまま。利用したければデータフレームに代入する
df2
Out[100]:
4×2 DataFrame
Rowxy
Int64Int64
11121
24144
39169
416196
In [101]:
mapcols!(a -> a.^2, df) # インプレースで関数が適用され,df は破壊される
df
Out[101]:
4×2 DataFrame
Rowxy
Int64Int64
11121
24144
39169
416196

ソート(並べ替え)¶

In [102]:
df = DataFrame(a = [1,1,2,2,3,3], b = [1,2,1,2,4,3],
    c = [11,12,13,14,15,16], d = [21,-22,-23,24,25,26],
    name = 'f':-1:'a')
Out[102]:
6×5 DataFrame
Rowabcdname
Int64Int64Int64Int64Char
1111121f
21212-22e
32113-23d
4221424c
5341525b
6331626a

ソートされているかをチェックする¶

In [103]:
issorted(df, :name) # :name 列は昇順にソートされているか?(降順にソートされているので false)
Out[103]:
false
In [104]:
issorted(df, 3) # 3 列目は昇順にソートされているか? true
Out[104]:
true

ソートの順番を決める¶

sort!() はインプレースでソートされる。

In [105]:
sort(df, order(:name)) # :name を昇順にソート
Out[105]:
6×5 DataFrame
Rowabcdname
Int64Int64Int64Int64Char
1331626a
2341525b
3221424c
42113-23d
51212-22e
6111121f
In [106]:
sort(df, order(:b, rev=true)) # :b 列を降順にソート
Out[106]:
6×5 DataFrame
Rowabcdname
Int64Int64Int64Int64Char
1341525b
2331626a
31212-22e
4221424c
5111121f
62113-23d
In [107]:
sort(df, order(:d, by=abs)) # :d 列の絶対値をとって降順にソート
Out[107]:
6×5 DataFrame
Rowabcdname
Int64Int64Int64Int64Char
1111121f
21212-22e
32113-23d
4221424c
5341525b
6331626a

並べ替えベクトルを返す¶

In [108]:
o = sortperm(df, :d)
o |> println
[3, 2, 1, 4, 5, 6]

sortperm() で求めたベクトルに基づいて,ソートすることができる。

In [109]:
df[o, :]
Out[109]:
6×5 DataFrame
Rowabcdname
Int64Int64Int64Int64Char
12113-23d
21212-22e
3111121f
4221424c
5341525b
6331626a
In [110]:
sort(df, :d) # o = sortperm(df, :d); df[o, :] と同じ
Out[110]:
6×5 DataFrame
Rowabcdname
Int64Int64Int64Int64Char
12113-23d
21212-22e
3111121f
4221424c
5341525b
6331626a

行の包含¶

In [111]:
df1 = DataFrame(ID = [1, 2, 3], Name = ["Karl Pearson", "Maurice Kendall", "Charles Spearman"])
Out[111]:
3×2 DataFrame
RowIDName
Int64String
11Karl Pearson
22Maurice Kendall
33Charles Spearman
In [112]:
df2 = DataFrame(ID = [1, 2, 4], Job = ["Lawyer", "Doctor", "Farmer"])
Out[112]:
3×2 DataFrame
RowIDJob
Int64String
11Lawyer
22Doctor
34Farmer

df1 と df2 のすべての組み合わせを作る crossjoin¶

R の expand.grid に相当するもの。引数はデータフレームでなければならない。

In [113]:
crossjoin(DataFrame(a = df1.Name), DataFrame(b = df2.Job))
Out[113]:
9×2 DataFrame
Rowab
StringString
1Karl PearsonLawyer
2Karl PearsonDoctor
3Karl PearsonFarmer
4Maurice KendallLawyer
5Maurice KendallDoctor
6Maurice KendallFarmer
7Charles SpearmanLawyer
8Charles SpearmanDoctor
9Charles SpearmanFarmer

df1 の行のうち,df2 に含まれない行を抽出する antijoin¶

on で指定する行列をキーとして,df1 の行のうち,df2 に含まれない行を抽出する

In [114]:
antijoin(df1, df2, on=:ID)
Out[114]:
1×2 DataFrame
RowIDName
Int64String
13Charles Spearman

df1 に,df2 をマージする innerjoin¶

on で指定する行列をキーとして,df1 と df2 に共通する行を取り出す。

In [115]:
innerjoin(df1, df2, on=:ID)
Out[115]:
2×3 DataFrame
RowIDNameJob
Int64StringString
11Karl PearsonLawyer
22Maurice KendallDoctor

df1 に,df2 をマージする leftjoin¶

on で指定する行列をキーとして,df1 に df2 をマージする。df2 にない項目は missing になる。

In [116]:
leftjoin(df1, df2, on=:ID)
Out[116]:
3×3 DataFrame
RowIDNameJob
Int64StringString?
11Karl PearsonLawyer
22Maurice KendallDoctor
33Charles Spearmanmissing

df1 に,df2 をマージする outerjoin¶

on で指定する行列をキーとして,df1 に df2 をマージする。どちらかのデータフレームにない項目は missing になる。

In [117]:
outerjoin(df1, df2, on=:ID)
Out[117]:
4×3 DataFrame
RowIDNameJob
Int64String?String?
11Karl PearsonLawyer
22Maurice KendallDoctor
33Charles Spearmanmissing
44missingFarmer

df2 に,df1 をマージする rightjoin¶

on で指定する行列をキーとして,df1 に df2 をマージする。df1 にない項目は missing になる。

In [118]:
rightjoin(df1, df2, on=:ID)
Out[118]:
3×3 DataFrame
RowIDNameJob
Int64String?String
11Karl PearsonLawyer
22Maurice KendallDoctor
34missingFarmer

両方に存在する項目のみでマージする semijoin¶

on で指定する行列をキーとして,df1 と df2 の両方に存在する項目のみでマージする。

In [119]:
semijoin(df1, df2, on=:ID)
Out[119]:
2×2 DataFrame
RowIDName
Int64String
11Karl Pearson
22Maurice Kendall

欠損値のリストワイズ除去¶

欠損値のリストワイズ除去とは,1 行中に 1 つでも欠損値を含む行を除去することである。

In [120]:
airquality2 = dropmissing(airquality);
size(airquality)  |> println
size(airquality2) |> println
first(airquality2, 5)
(153, 6)
(111, 6)
Out[120]:
5×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64Int64Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453
41831311.56254
5232998.66557

dropmissing() では欠損値を除去した結果を別のデータフレームに(同じでもよいが)代入しなければ結果は保存されない。

一方 dropmissing!() では引数で指定したデータフレームが直接書き換えられる。

In [121]:
dropmissing!(airquality);
size(airquality) |> println
(111, 6)

ロングフォーマット(縦長データフレーム)に変換する¶

In [122]:
df = DataFrame(a = [1,1,2,2,3,3], b = [1,1,1,2,2,2],
    c = [11,12,13,14,15,16], d = [21,22,23,24,25,26],
    e = ["a", "b", "c", "d", "e", "f"])
Out[122]:
6×5 DataFrame
Rowabcde
Int64Int64Int64Int64String
1111121a
2111222b
3211323c
4221424d
5321525e
6321626f
In [123]:
df2 = stack(df, [:c, :d])
Out[123]:
12×5 DataFrame
Rowabevariablevalue
Int64Int64StringStringInt64
111ac11
211bc12
321cc13
422dc14
532ec15
632fc16
711ad21
811bd22
921cd23
1022dd24
1132ed25
1232fd26

ワイドフォーマット(横長データフレーム)に変換する¶

前節でロングフォーマットに変換したデータフレームを元のワイドフォーマットに変換する(元に戻る)。

In [124]:
unstack(df2)
Out[124]:
6×5 DataFrame
Rowabecd
Int64Int64StringInt64?Int64?
111a1121
211b1222
321c1323
422d1424
532e1525
632f1626

データフレームをグループ変数に基づいて分割する¶

groupby() により,第 1 引数のデータフレームを,第 2 引数で指定する列名でグループ分けする。列名は複数をベクトルで与えることもできる。

:Month が 5, 6, 7, 8, 9 の 5 つのデータフレームに分割する。

In [125]:
gd = groupby(airquality, :Month);

グループ分けされたデータフレームを抽出する¶

get() により,グループ分けに使った名前 :Month の列の値が 5 であるデータフレームを取り出す(最初の 3 行だけを示す)。

In [126]:
get(gd, (Month=5,), nothing)[1:3,:]
Out[126]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64Int64Float64Int64Int64Int64
1411907.46751
2361188.07252
31214912.67453

グループ分けに使った列の値が 6 のデータフレームを取り出す(最初の 3 行だけを示す)。

In [127]:
get(gd, (6,), nothing)[1:3,:]
Out[127]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64Int64Float64Int64Int64Int64
1291279.78267
27129113.89069
33932311.587610

グループ分けに使った列の値が 10 のデータフレームを取り出そうとすると,なにもないので結果が返ってこない。実は 3 番目の引数は,条件を満たすデータフレームがない場合に何を返すかを指定している。

In [128]:
get(gd, (10,), "なにもない")
Out[128]:
"なにもない"

size(gd) はグループ化されたデータファイルの個数を返す。

In [129]:
size(gd)
Out[129]:
(5,)

したがって,gd[n] のように指定すれば n 番目のグループ化されたデータフレームが得られる。

In [130]:
gd[2][1:3, :] # 最初の 3 行だけ示す
Out[130]:
3×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64Int64Float64Int64Int64Int64
1291279.78267
27129113.89069
33932311.587610

要するに,データフレームを要素とする配列なので,他に,gd[end],last(gd),gd[(Month=7,)],gd[Dict("Month" => 8)],gd[(9,)] などの指定ができる。

グループ化されたデータフレームに関する情報¶

groupcols()) はグループ化に使われた列名を返す。

In [131]:
groupcols(gd) |> println
[:Month]

groupindices() は元のデータフレムの各行が何番目のグループ化されたデータフレームに属するかのベクトルを返す(長さは元のデータフレームの行数)。

In [132]:
groupindices(gd) |> println
Union{Missing, Int64}[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]

keys() はグループデータフレームのキーの値を返す。

In [133]:
keys(gd)
Out[133]:
5-element DataFrames.GroupKeys{GroupedDataFrame{DataFrame}}:
 GroupKey: (Month = 5,)
 GroupKey: (Month = 6,)
 GroupKey: (Month = 7,)
 GroupKey: (Month = 8,)
 GroupKey: (Month = 9,)
In [134]:
k = keys(gd)[1]
Out[134]:
GroupKey: (Month = 5,)
In [135]:
keys(k), values(k)
Out[135]:
([:Month], (5,))
In [136]:
values(k)
Out[136]:
(5,)
In [137]:
NamedTuple(k)
Out[137]:
(Month = 5,)
In [138]:
k.Month, k[:Month], k[1]
Out[138]:
(5, 5, 5)

グループ化に使われなかった列名を返す。

In [139]:
valuecols(gd) |> println
[:Ozone, :Solar_R, :Wind, :Temp, :Day]

親データフレームを返す¶

グループ化されたデータフレームをまとめて元のデータフレームを再現する。

In [140]:
parent(gd)[end-4:end, :] # 最後の 5 行を示す
Out[140]:
5×6 DataFrame
RowOzoneSolar_RWindTempMonthDay
Int64Int64Float64Int64Int64Int64
1142016.663925
2301936.970926
31419114.375928
4181318.076929
52022311.568930

列ごとに関数を適用する¶

eachcol()

In [141]:
using Statistics
df = DataFrame(a = 1:5, b = 11:15)
mean.(eachcol(df))
Out[141]:
2-element Vector{Float64}:
  3.0
 13.0
In [142]:
std.(eachcol(df))
Out[142]:
2-element Vector{Float64}:
 1.5811388300841898
 1.5811388300841898
In [143]:
map(eachcol(df)) do col
                     maximum(col) - minimum(col)
                 end
Out[143]:
2-element Vector{Int64}:
 4
 4
In [144]:
map(x -> mean(skipmissing(x)), eachcol(airquality))
Out[144]:
6-element Vector{Float64}:
  42.0990990990991
 184.80180180180182
   9.939639639639632
  77.7927927927928
   7.216216216216216
  15.945945945945946

行ごとに関数を適用する¶

In [145]:
sum.(eachrow(df))
Out[145]:
5-element Vector{Int64}:
 12
 14
 16
 18
 20
In [146]:
mean.(eachrow(df))
Out[146]:
5-element Vector{Float64}:
  6.0
  7.0
  8.0
  9.0
 10.0

クエリーによる操作例¶

スタンドアローンのクエリー演算子はパイプ演算子 |> で組み合わせて使われる。

In [147]:
using Query, DataFrames, Statistics
df = DataFrame(a = [1, 1, 2, 3], b = [4, 5, 6, 8])
Out[147]:
4×2 DataFrame
Rowab
Int64Int64
114
215
326
438
In [148]:
df2 = df |>
           @groupby(_.a) |>
           @map({a=key(_), b=mean(_.b)}) |>
           @filter(_.b > 5) |>
           @orderby_descending(_.b) |>
           DataFrame
Out[148]:
2×2 DataFrame
Rowab
Int64Float64
138.0
226.0

要素に関数を適用する @map コマンド¶

source |> @map(element_selector)

source は,クエリーできるものなら何でもよい(これ以降の全てのコマンドの場合も同じ)。

下の例ではベクトルが対象にされている。

以下の例は,ベクトルから要素を 1 個ずつ取り出し 2 乗して,結果を集め,x に代入する。

In [149]:
data = [1,2,3];
x = data |> @map(_^2) |> collect
x |> println
[1, 4, 9]

条件を満たす行を抽出 @filter コマンド¶

source |> @filter(filter_condition)

filter_condition は,ソースから 1 つの要素を受け取りその要素を保持する場合に true,フィルターされる場合には false を返す無名関数

In [150]:
df = DataFrame(name=["John", "Sally", "Kirk"],
               age=[23., 42., 59.], children=[3,5,2])
Out[150]:
3×3 DataFrame
Rownameagechildren
StringFloat64Int64
1John23.03
2Sally42.05
3Kirk59.02
In [151]:
x = df |> @filter(_.age > 30 && _.children > 2) |> DataFrame
Out[151]:
1×3 DataFrame
Rownameagechildren
StringFloat64Int64
1Sally42.05

データフレームのグループ化 @groupby コマンド¶

source |> @groupby(key_selector)

key_selector は,ソースの要素がグループ化されるときに使われる値を返す無名関数

source |> @groupby(source, key_selector, element_selector)

element_selector は,ソースの要素がグループ分けされる前にグループの前に挿入される要素に適用される無名関数

@group の戻り値はグループのイテラブルである。

In [152]:
df = DataFrame(name=["John", "Sally", "Kirk"],
               age=[23., 42., 59.], children=[3,2,2])
Out[152]:
3×3 DataFrame
Rownameagechildren
StringFloat64Int64
1John23.03
2Sally42.02
3Kirk59.02
In [153]:
x = df |>
           @groupby(_.children) |> # children でグループ化
           @map({Key=key(_), Count=length(_)}) |> # Count はサイズ
           DataFrame
Out[153]:
2×2 DataFrame
RowKeyCount
Int64Int64
131
222

データのソート @orderby,@orderby_descenidng,@thenby,@thenby_descending コマンド¶

@orderby コマンド
@orderby_descenidng コマンド

データのソート(昇順,降順)

@thenby コマンド
@thenby_descending コマンド

直前のデータソートに続いてデータのソートを行う(昇順,降順)。直前のソートで同順位があった場合の処理を定義する。

source |> @orderby(key_selector)

@orderby(key_selector) |> @thenby(key_selector2)

In [154]:
df = DataFrame(a=[2,1,1,2,1,3],b=[2,2,1,1,3,2])
x = df |> @orderby_descending(_.a) |> @thenby(_.b) |> DataFrame
Out[154]:
6×2 DataFrame
Rowab
Int64Int64
132
221
322
411
512
613

データフレームのマージ @groupjoin コマンド¶

outer |> @groupjoin(inner, outer_selector, inner_selector, result_selector)

outer,inner はクエリーできるものなら何でもよい

In [155]:
df1 = DataFrame(a=[1,2,3], b=[1.,2.,3.])
Out[155]:
3×2 DataFrame
Rowab
Int64Float64
111.0
222.0
333.0
In [156]:
df2 = DataFrame(c=[2,4,2], d=["John", "Jim","Sally"])
Out[156]:
3×2 DataFrame
Rowcd
Int64String
12John
24Jim
32Sally
In [157]:
x = df1 |> @groupjoin(df2, _.a, _.c, {t1=_.a, t2=length(__)}) |> DataFrame
Out[157]:
3×2 DataFrame
Rowt1t2
Int64Int64
110
222
330

データフレームの連結 @join コマンド¶

In [158]:
df1 = DataFrame(a=[1,2,3], b=[1.,2.,3.])
df2 = DataFrame(c=[2,4,2], d=["John", "Jim","Sally"])
x = df1 |> @join(df2, _.a, _.c, {_.a, _.b, __.c, __.d}) |> DataFrame
Out[158]:
2×4 DataFrame
Rowabcd
Int64Float64Int64String
122.02John
222.02Sally

キーと値のペアを展開 @mapmany コマンド¶

In [159]:
source = Dict(:a=>[1,2,3], :b=>[4,5])
Out[159]:
Dict{Symbol, Vector{Int64}} with 2 entries:
  :a => [1, 2, 3]
  :b => [4, 5]
In [160]:
q = source |> @mapmany(_.second, {Key=_.first, Value=__}) |> DataFrame
Out[160]:
5×2 DataFrame
RowKeyValue
SymbolInt64
1a1
2a2
3a3
4b4
5b5

要素を取り出す @take コマンド¶

最初から幾つの要素を取り出すかを指示する

In [161]:
source = [1,2,3,4,5]
q = source |> @take(3) |> collect
Out[161]:
3-element Vector{Int64}:
 1
 2
 3

要素を捨てる @drop コマンド¶

最初から幾つの要素を捨てるかを指示する。

In [162]:
source = [1,2,3,4,5]
q = source |> @drop(3) |> collect
Out[162]:
2-element Vector{Int64}:
 4
 5

重複データを除く @unique コマンド¶

重複データを取り除く(ユニークなデータのみを残す)

In [163]:
source = [1,1,2,2,3]
q = source |> @unique() |> collect
Out[163]:
3-element Vector{Int64}:
 1
 2
 3

列の選択 @select コマンド¶

列の選択

source |> @select(selectors...)

selector は名前,位置,述語関数で選択,排除を指定する,これらは順に適用される(順序が変更されることはない)。

In [164]:
df = DataFrame(fruit=["Apple","Banana","Cherry"],amount=[2,6,1000],
                     price=[1.2,2.0,0.4],isyellow=[false,true,false])
q1 = df |> @select(2:3, occursin("ui"), -:amount) |> DataFrame
Out[164]:
3×2 DataFrame
Rowpricefruit
Float64String
11.2Apple
22.0Banana
30.4Cherry
In [165]:
df = DataFrame(fruit=["Apple","Banana","Cherry"],amount=[2,6,1000],
                     price=[1.2,2.0,0.4],isyellow=[false,true,false])
q2 = df |> @select(!endswith("t"), 1) |> DataFrame
Out[165]:
3×3 DataFrame
Rowpriceisyellowfruit
Float64BoolString
11.2falseApple
22.0trueBanana
30.4falseCherry

列名の変更 @rename コマンド¶

列名の変更

source |> @rename(args...)

args は順に適用される

In [166]:
df = DataFrame(fruit=["Apple","Banana","Cherry"],amount=[2,6,1000],
                     price=[1.2,2.0,0.4],isyellow=[false,true,false])
q = df |> @rename(:fruit => :food, :price => :cost, :food => :name) |> DataFrame
Out[166]:
3×4 DataFrame
Rownameamountcostisyellow
StringInt64Float64Bool
1Apple21.2false
2Banana62.0true
3Cherry10000.4false

変数変換 @mutate コマンド¶

数式で変数変換

In [167]:
df = DataFrame(fruit=["Apple","Banana","Cherry"],amount=[2,6,1000],
                     price=[1.2,2.0,0.4],isyellow=[false,true,false])
q = df |> @mutate(price = 2 * _.price + _.amount, isyellow = _.fruit == "Apple") |> DataFrame
Out[167]:
3×4 DataFrame
Rowfruitamountpriceisyellow
StringInt64Float64Bool
1Apple24.4true
2Banana610.0false
3Cherry10001000.8false

欠損値行を除く @dropna コマンド¶

欠損値のある行を除く

source |> @dropna(columns...)

引数なしで @dropna() のように使用されるときは,全ての列が対象になる。

In [168]:
df = DataFrame(a=[1,2,3], b=[4,missing,5])
q = df |> @dropna() |> DataFrame
Out[168]:
2×2 DataFrame
Rowab
Int64Int64
114
235
In [169]:
q = df |> @dropna(:b) |> DataFrame
Out[169]:
2×2 DataFrame
Rowab
Int64Int64
114
235
In [170]:
q = df |> @dropna(:b, :a) |> DataFrame
Out[170]:
2×2 DataFrame
Rowab
Int64Int64
114
235

欠損値を含まないデータフレーム @dissallowna コマンド¶

欠損値を含まないデータフレームとする

source |> @dissallowna(columns...)

@dissallowna() のように引数なしで使われると全ての列を対象にする。

なお,以下で Query.isna() としているが,これは同じ名前の関数が同時に使用している RCall パッケージにもあるため,「Query の isna() である」ことを明示しているだけなので,重複がなければ isna() だけでよい。

In [171]:
df = DataFrame(a=[1,missing,3], b=[4,5,6])
q = df |> @filter(!Query.isna(_.a)) |> @dissallowna() |> DataFrame
Out[171]:
2×2 DataFrame
Rowab
Int64Int64
114
236
In [172]:
df = DataFrame(a=[1,2,missing], b=[4,missing,5])
q = df |> @filter(!Query.isna(_.b)) |> @dissallowna(:b) |> DataFrame
Out[172]:
2×2 DataFrame
Rowab
Int64?Int64
114
2missing5

欠損値の置き換え @replacena コマンド¶

欠損値の置き換え

source |> @replacena(replacement_value)

In [173]:
df = DataFrame(a=[1,missing,3], b=[4,5,6])
q = df |> @replacena(0) |> DataFrame
Out[173]:
3×2 DataFrame
Rowab
Int64Int64
114
205
336
In [174]:
df = DataFrame(a=[1,2,missing], b=["One",missing,"Three"])
q = df |> @replacena(:b=>"Unknown", :a=>0) |> DataFrame
Out[174]:
3×2 DataFrame
Rowab
Int64String
11One
22Unknown
30Three

データフレームを二次元配列に変換する¶

Matrix() を使用する。データフレームの各列の型が違う場合(特に数値型と文字列型が混在するような場合)には,R のように最上位の型に統一されることはない。

In [175]:
M = Matrix(airquality);
M[1:5, :]
Out[175]:
5×6 Matrix{Float64}:
 41.0  190.0   7.4  67.0  5.0  1.0
 36.0  118.0   8.0  72.0  5.0  2.0
 12.0  149.0  12.6  74.0  5.0  3.0
 18.0  313.0  11.5  62.0  5.0  4.0
 23.0  299.0   8.6  65.0  5.0  7.0
In [176]:
M2 = Matrix(iris[1:3, :])
Out[176]:
3×5 Matrix{Any}:
 5.1  3.5  1.4  0.2  "setosa"
 4.9  3.0  1.4  0.2  "setosa"
 4.7  3.2  1.3  0.2  "setosa"