q/kdb+の処理をJuliaで書く

列指向インメモリDB、kdb+の埋め込み言語であるqでは、シンプルな表現で様々な処理を書くことができます
最近Juliaを利用する中で、qの標準関数で出来るのにJuliaで出来ない！ということが多かったので自分用にまとめます。
Juliaで壁にぶち当たる度に増えていくかもしれません

#使用環境
Julia: 1.5.1
DataFrames: 0.21.7

kdb+: 32bit 3.6

#標準関数

##文字列の結合(,)

文字列を結合します

```kdb+:q
q)"abc","def"
"abcdef"
```

Juliaの場合は*演算子になります

```julia:julia
julia> "abc"*"def"
"abcdef"
```

なお、kdbの結合は厳密にはリストやAtomに対する結合ですので、
数値リストの結合も同じ演算子で可能です

```kdb+:q
q)(1 2 3),(4 5 6)
1 2 3 4 5 6
```

##連番：til
0からスタートする連番を生成します

```kdb+:q
q)til 10
0 1 2 3 4 5 6 7 8 9
```

Juliaではrange, collectを使うのが近いでしょうか

```julia:julia
julia> collect(0:9)
10-element Array{Int64,1}:
 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
```

## 累計：sums
リストの累計を算出します

```kdb+
q)sums 1 + til 10
1 3 6 10 15 21 28 36 45 55
```

Juliaではcumsumが使えます

```julia
julia> cumsum(1:10)
10-element Array{Int64,1}:
  1
  3
  6
 10
 15
 21
 28
 36
 45
 55
```

## each

関数を修飾し、リスト内の全ての値に対して処理を実行します

```kdb+:q
q)count each ("ab"; "cde"; "fghi")
2 3 4
```

JuliaではBroadcast(.)を使います

```julia:julia
julia> length.(["ab", "cde", "fghi"])
3-element Array{Int64,1}:
 2
 3
 4
```

## each-left(\\:), each-right(/:), each-both(')

2つの引数を持つ関数を修飾し、左、右、その両方のリスト内要素それぞれに関数を適用します

```kdb+:q
q)/each-left
q)"12345",\:"ABC"
"1ABC"
"2ABC"
"3ABC"
"4ABC"
"5ABC"

q)/each-right
q)"12345",/:"ABC"
"12345A"
"12345B"
"12345C"

q)/each-both
q)"123",'"ABC"
"1A"
"2B"
"3C"

q)/each-left,right
q)"12345",/:\:"ABC"
"1A" "1B" "1C"
"2A" "2B" "2C"
"3A" "3B" "3C"
"4A" "4B" "4C"
"5A" "5B" "5C"
```

標準では見つからなかったので、
色々考えましたがJuliaだとmapやforを使って以下のように書くしかないでしょうか

```julia:julia
julia> #each-left
julia> map(x -> *(x, "ABC"), collect("12345"))
5-element Array{String,1}:
 "1ABC"
 "2ABC"
 "3ABC"
 "4ABC"
 "5ABC"

julia> #each-right
julia> map(x -> *("12345", x), collect("ABC"))
3-element Array{String,1}:
 "12345A"
 "12345B"
 "12345C"

julia> #each-both
julia> map((x,y)->*(x, y), "123", "ABC")
3-element Array{String,1}:
 "1A"
 "2B"
 "3C"

julia> #each-left,right
julia> [x*y for x in "12345" for y in "ABC"]
15-element Array{String,1}:
 "1A"
 "1B"
 "1C"
 "2A"
 "2B"
 "2C"
 "3A"
 "3B"
 "3C"
 "4A"
 "4B"
 "4C"
 "5A"
 "5B"
 "5C"
```

上記は文字列結合を例にしましたが、Juliaの文字列は文字型の配列という側面もありながらも、型としてはStringになるため、少し特殊な例と言えるかもしれません。
そもそも修飾する関数が左辺、右辺にどのような型を取れるかによって、各イテレーションにおける挙動もそれに応じて変わりますので、結局はそこに依存することにはなります。
例えば、each-bothのような処理は、数値演算であればJuliaでもBroadcast(.)だけで記述できます

```kdb+:q
q)(1 2 3)+'4 5 6
5 7 9
```

```julia:julia
julia> [1, 2, 3].+[4, 5, 6]
3-element Array{Int64,1}:
 5
 7
 9
```

2020/09/12　追記
文字列結合であればよりシンプルな記述をコメントいただきました。
ありがとうございます。

```julia:julia
julia> collect("12345") .* "ABC"
5-element Array{String,1}:
 "1ABC"
 "2ABC"
 "3ABC"
 "4ABC"
 "5ABC"

julia> "12345" .* collect("ABC")
3-element Array{String,1}:
 "12345A"
 "12345B"
 "12345C"

julia> collect("123") .* collect("ABC")
3-element Array{String,1}:
 "1A"
 "2B"
 "3C"

julia> collect("12345") .* reshape(collect("ABC"), 1, :)
5×3 Array{String,2}:
 "1A"  "1B"  "1C"
 "2A"  "2B"  "2C"
 "3A"  "3B"  "3C"
 "4A"  "4B"  "4C"
 "5A"  "5B"  "5C"
```

## リストの次元を減らす(raze)

2次元の配列を1次元配列に直列化します

```kdb+:q
q)raze (1 2 3; 4 5 6)
1 2 3 4 5 6
```

Juliaの場合vecが使えます。
Juliaの場合は配列の配列と行列は異なる型になりますが、vecが期待通りに動くのは行列に対してです。

```julia:julia
julia> vec([[1 2 3];[4 5 6]]')
6-element reshape(::LinearAlgebra.Adjoint{Int64,Array{Int64,2}}, 6) with eltype Int64:
 1
 2
 3
 4
 5
 6
```

Juliaの行列はcolumn-major orderとのことですので、普通にvecをしてしまうと
```1 4 2 5 3 6```という順番になってしまいます。（行列の左上から下に読んでいく）
そのため、転置(')をしてからvec関数に通しています。

また、厳密にArray型にするのであれば、更にcollect関数を通します

```julia:julia
julia> collect(vec([[1 2 3];[4 5 6]]'))
6-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
```

##累積計算(scan)

関数を修飾することで、2つの引数を取る任意の関数を累積計算させることができます

```kdb+:q
q)(+) scan 1 2 3 4 5 6 7 8 9 10
1 3 6 10 15 21 28 36 45 55
```

Juliaではaccumulateを使います

```julia:julia
julia> accumulate(+, [1,2,3,4,5,6,7,8,9,10])
10-element Array{Int64,1}:
  1
  3
  6
 10
 15
 21
 28
 36
 45
 55
```

ちなみに今回の例は足し算ですが、足し算であれば既に述べた通り
qであれば```sums```が、Juliaであれば```cumsum```がありますので
わざわざ```scan```や```accumulate```を使う必要はありません

##欠損値を直前の値で埋める（fills）

リスト内に欠損値があった場合に手前の（欠損値ではない）値で補完します。

```kdb+:q
q)fills 0N 1 9 3 0N 0N 7 8 0N 4
0N 1 9 3 3 3 7 8 8 4
```

Juliaですと関数を作ってあげるしかないでしょうか

```julia:julia
julia> function fills!(arr)
           for i in 2:length(arr)
               if ismissing(arr[i])
                   arr[i] = arr[i-1]
               end
           end
       end
fills! (generic function with 1 method)
julia> arr = [missing, 1, 9, 3, missing, missing, 7, 8, missing, 4]
10-element Array{Union{Missing, Int64},1}:
  missing
 1
 9
 3
  missing
  missing
 7
 8
  missing
 4
julia> fills!(arr)
julia> arr
10-element Array{Union{Missing, Int64},1}:
  missing
 1
 9
 3
 3
 3
 7
 8
 8
 4
```

#xbar

```x xbar y```とした時、yに最も近いxの倍数に切り捨てます。

```kdb+:q
q)4 xbar 1 2 3 4 5 6 7 8 9 10
0 0 0 4 4 4 4 8 8 8
```

そのものは見つからなかったのでJuliaでやるとすれば関数を新たに作成することになりそうです。さらに、Floatに対して適用する場合はもう少し工夫が要りそうです。

```julia:julia
julia> function xbar(x, y)
           return y ÷ x * x
       end
xbar (generic function with 1 method)

julia> xbar.(4, [1,2,3,4,5,6,7,8,9,10])
10-element Array{Int64,1}:
 0
 0
 0
 4
 4
 4
 4
 8
 8
 8
```

kdb+でxbarを使うときは時間に対して使うことも多いと思いますが、
そちらはjuliaもDatesで実装されているroundが問題なく使えそうです

```kdb+:q
q)/10分毎の単位で切り捨て
q)0D00:10:00 xbar 2020.09.01D10:15:20.000000000
2020.09.01D10:10:00.000000000
```

```julia:julia
julia> using Dates
julia> round(DateTime(2020, 9, 1, 10, 15, 20), Dates.Minute(10), RoundDown)
2020-09-01T10:10:00
```

kdb+は時系列DBということもあり、日付・時間リテラルもサポートされている分書きやすいですね。

#構文

##関数作成

```kdb+:q
q)f: {[a; b] a + b}
q)f[5;6]
11
```

```julia:julia
julia> function f(a, b)
           return a + b
       end
f (generic function with 1 method)
julia> f(5, 6)
11
```

##関数のプロジェクション

関数の引数の一部のみを固定して新たな関数を作成する機能です
※先ほどの関数```f```を流用します

```kdb+:q
q)g: f[5;]
q)g[6]
11
```

```julia:julia
julia> g(b) = f(5, b)
g (generic function with 1 method)
julia> g(6)
11
```

2020/09/17 追記
コメントで別の方法を教えていただきました。
ありがとうございます

```julia:julia
julia> f(x,y)=x/y
f (generic function with 1 method)

julia> g=Base.Fix1(f,2)
(::Base.Fix1{typeof(f),Int64}) (generic function with 1 method)

julia> g(1)  # => 2/1
2.0

julia> h=Base.Fix2(f,2)
(::Base.Fix1{typeof(f),Int64}) (generic function with 1 method)

julia> h(1)  # => 1/2
0.5
```

##無名関数

```kdb+:q
q){[a; b] a + b}[5; 6]
11
```

```julia:julia
julia> ((a, b) -> a + b)(5, 6)
11
```

#Table・DataFrame関連

kdb+はインメモリデータベースということもあって、PythonやJuliaで言うDataFrameの概念がTableとして標準で用意されています。
sqlライクに問い合わせが可能なq-sqlも標準の構文として存在しているため高い汎用性があります。

##テーブル作成

```kdb+:q
q)tab: ([] a: `A`B`C`D`E; b:1 2 3 4 5)
q)tab
a b
---
A 1
B 2
C 3
D 4
E 5
```

```julia:julia
julia> using DataFrames
julia> df = DataFrame(a = [:A, :B, :C, :D, :E], b = [1, 2, 3, 4, 5])
5×2 DataFrame
│ Row │ a      │ b     │
│     │ Symbol │ Int64 │
├─────┼────────┼───────┤
│ 1   │ A      │ 1     │
│ 2   │ B      │ 2     │
│ 3   │ C      │ 3     │
│ 4   │ D      │ 4     │
│ 5   │ E      │ 5     │
```

##1列抽出する

テーブル・データフレームから1列抽出します。
qの場合列を指定するほかに、q-sqlのexecを使うことでも抽出できます
※先ほど作成したテーブル、データフレームを流用しています

```kdb+:q
q)tab.a
`A`B`C`D`E
q)exec a from tab
`A`B`C`D`E
```

Juliaも同様にドットで列にアクセス出来ます
DataFrameに問い合わせを行うためのパッケージはいくつかあるようですが、
ここではDataFramesMetaのwithマクロを併せて記載します。

```julia:julia
julia> df.a
5-element Array{Symbol,1}:
 :A
 :B
 :C
 :D
 :E

julia> using DataFramesMeta
julia> @with(df, :a)
5-element Array{Symbol,1}:
 :A
 :B
 :C
 :D
 :E
```

##抽出(select)

###条件無しで取得

qでは、q-sqlによりほぼsqlと同じように問い合わせが出来ます

```kdb+:q
q)select a, b from tab
a b
---
A 1
B 2
C 3
D 4
E 5
```

Juliaでは例えば以下のように取得できます

```julia:julia
julia> #DataFrame標準
julia> df[[:a, :b]]
5×2 DataFrame
│ Row │ a      │ b     │
│     │ Symbol │ Int64 │
├─────┼────────┼───────┤
│ 1   │ A      │ 1     │
│ 2   │ B      │ 2     │
│ 3   │ C      │ 3     │
│ 4   │ D      │ 4     │
│ 5   │ E      │ 5     │

julia> #DataFramesMeta
julia> @select(df, :a, :b)
5×2 DataFrame
│ Row │ a      │ b     │
│     │ Symbol │ Int64 │
├─────┼────────┼───────┤
│ 1   │ A      │ 1     │
│ 2   │ B      │ 2     │
│ 3   │ C      │ 3     │
│ 4   │ D      │ 4     │
│ 5   │ E      │ 5     │
```

###where句

qについてはこれもほぼSQLと同様です
（全ての列を取得するときにアスタリスクを書かないことくらいです）

```kdb+:q
q)select from tab where a = `C
a b
---
C 3
```

先ほどと同じく、DataFrame単体でやる場合、DataFramesMetaでそれぞれ以下の通りです

```julia:julia
julia> #DataFrame
julia> df[df.a .== Symbol("C"), :]
1×2 DataFrame
│ Row │ a      │ b     │
│     │ Symbol │ Int64 │
├─────┼────────┼───────┤
│ 1   │ C      │ 3     │

julia> #DataFramesMeta
julia> @where(df, :a .== Symbol("C"))
1×2 DataFrame
│ Row │ a      │ b     │
│     │ Symbol │ Int64 │
├─────┼────────┼───────┤
│ 1   │ C      │ 3     │
```

qの場合以下のような方法もあります。

```kdb+:q
q)tab[where tab.a = `C]
a b
---
C 3

q)@[tab; where tab.a = `C]
a b
---
C 3
```

また、更に取得する列を絞る場合は以下のようになります

```kdb+:q
q)select b from tab where a = `C
b
-
3
```

```julia:julia
julia> @select(@where(df, :a .== Symbol("C")), :b)
1×1 DataFrame
│ Row │ b     │
│     │ Int64 │
├─────┼───────┤
│ 1   │ 3     │
```

ただ、Juliaの場合は@linqを使って以下のように書くほうが可読性の面では良さそうです

```julia:julia
julia> @linq df |>
           where(:a .== Symbol("C")) |>
           select(:b)
1×1 DataFrame
│ Row │ b     │
│     │ Int64 │
├─────┼───────┤
│ 1   │ 3     │
```


#参考

##Julia
[Juliaで最低限やっていくための配列操作まとめ](https://qiita.com/A03ki/items/007be353411d19952ef7)
[Dates](https://docs.julialang.org/en/v1/stdlib/Dates/#Rounding-Epoch)
[Github - JuliaData/DataFramesMeta.jl](https://juliadata.github.io/DataFrames.jl/stable/man/querying_frameworks/)

##kdb+/q
[Map Iterators](https://code.kx.com/q/ref/maps/)