ジュリアは最も若い数学的プログラミング言語の1つであり、この分野の主要なプログラミング言語であると主張しています。 残念ながら、現時点ではロシア語で十分な文献がなく、英語で入手可能な資料には、ジュリアの動的な開発のために現在のバージョンに必ずしも対応しない情報が含まれていますが、初心者のジュリアプログラマーにとってはこれは明らかではありません。 ギャップを埋め、簡単な例の形で読者にジュリアのアイデアを伝えようとします。
この記事の目的は、読者にJuliaプログラミング言語でテーブルを操作する基本的な方法のアイデアを提供し、実際のデータを処理するためにこのプログラミング言語の使用を開始するよう促すことです。 読者はすでに他のプログラミング言語に精通していることを前提としているため、これがどのように行われるかについて最小限の情報のみを提供しますが、データ処理方法の詳細については説明しません。
もちろん、データ分析を実行するプログラムの作業で最も重要な段階の1つは、インポートとエクスポートです。 さらに、最も一般的なデータ表示形式はテーブルです。 Juliaには、リレーショナルDBMSへのアクセスを提供し、HDF5、MATLAB、JLDなどの交換フォーマットを使用するライブラリがあります。 ただし、この場合は、CSVなどのテーブルを表すテキスト形式にのみ関心があります。
テーブルを見る前に、このデータ構造のプレゼンテーションを少し紹介する必要があります。 Juliaの場合、テーブルは2次元配列またはDataFrameとして表すことができます。
配列
ジュリアの配列から始めましょう。 要素の番号付けは1から始まります。 これは数学者にとって非常に自然であり、さらに、Fortran、Pascal、Matlabでも同じスキームが使用されます。 これらの言語を使用したことがないプログラマーにとって、この番号付けは不快に思われ、境界条件を記述するときにエラーを引き起こすかもしれませんが、実際には、これは単なる習慣の問題です。 ジュリアを数週間使用した後、言語モデル間の切り替えの問題は発生しなくなりました。
この言語の2番目の重要な点は、配列の内部表現です。 Juliaにとって、線形配列は列です。 同時に、C、Javaなどの言語では、1次元配列は文字列です。
これをコマンドライン(REPL)で作成した配列で説明します
julia> a = [1, 2, 3] 3-element Array{Int64,1}: 1 2 3
配列のタイプに注意してください-配列{Int64,1}。 配列は1次元で、Int64型です。 さらに、この配列を別の配列と組み合わせたい場合は、列を扱っているため、vcat関数(つまり、垂直連結)を使用する必要があります。 結果は新しい列です。
julia> b = vcat(a, [5, 6, 7]) 7-element Array{Int64,1}: 1 2 3 5 6 7
配列を文字列として作成する場合、リテラルを記述するときに、コンマの代わりにスペースを使用して、配列{Int64,2}の2次元配列を取得します。 型宣言の2番目の引数は、多次元配列の座標の数を意味します。
julia> c = [1 2 3] 1×3 Array{Int64,2}: 1 2 3
つまり、1行3列のマトリックスを取得しました。
この行と列の表示もFortranとMatlabの特徴ですが、Juliaはアプリケーションの分野に特化した言語であることを思い出してください。
Juliaのマトリックスは、すべてのセルが同じタイプの2次元配列です。 Int64、Float64、さらにはStringなど、型が抽象Anyまたは非常に具体的である可能性があることに注意してみましょう。
リテラルの形式でマトリックスを作成できます。
julia> a = [1 2; 3 4] 2×2 Array{Int64,2}: 1 2 3 4
コンストラクターを使用して作成し、初期化なしでメモリを割り当てます(undef):
julia> a = Array{Int64,2}(undef, 2, 3) 2×3 Array{Int64,2}: 4783881648 4783881712 4782818640 4783881680 4783881744 4782818576
または、undefの代わりに特定の値が指定されている場合は初期化。
別々の列からの接着:
julia> a = [1, 2, 3] 3-element Array{Int64,1}: 1 2 3 julia> b = hcat(a, a, a, a) 3×4 Array{Int64,2}: 1 1 1 1 2 2 2 2 3 3 3 3
ランダムに初期化する:
julia> x = rand(1:10, 2, 3) 2×3 Array{Int64,2}: 1 10 2 9 7 7
引数rand-1〜10の範囲で、次元2 x 3。
または、包含を使用します(内包表記)
julia> x = [min(i, j) for i = 0:2, j = 0:2 ] 3×3 Array{Int64,2}: 0 0 0 0 1 1 0 1 2
ジュリアの列はメモリの線形ブロックであるという事実は、列ごとの要素の反復が行をソートするよりもはるかに高速であるという事実につながることに注意してください。 特に、次の例では、1_000_000行と100列のマトリックスを使用しています。
#!/usr/bin/env julia using BenchmarkTools x = rand(1:1000, 1_000_000, 100) #x = rand(1_000_000, 100) function sumbycolumns(x) sum = 0 rows, cols = size(x) for j = 1:cols, i = 1:rows sum += x[i, j] end return sum end @show @btime sumbycolumns(x) function sumbyrows(x) sum = 0 rows, cols = size(x) for i = 1:rows, j = 1:cols sum += x[i, j] end return sum end @show @btime sumbyrows(x)
結果:
74.378 ms (1 allocation: 16 bytes) =# @btime(sumbycolumns(x)) = 50053093495 206.346 ms (1 allocation: 16 bytes) =# @btime(sumbyrows(x)) = 50053093495
この例の@btimeは、実行にかかる平均時間を計算する関数の複数の実行です。 このマクロはBenchmarkTools.jlライブラリによって提供されます。 Juliaベースキットには時間マクロがありますが、単一の間隔を測定します。この場合、不正確になります。 showマクロは、単に式とその計算値をコンソールに表示します。
列ストレージの最適化は、テーブルで統計操作を実行するのに便利です。 従来、テーブルは列の数によって制限され、行の数は任意であるため、平均値、最小値、最大値の計算などのほとんどの操作は、行ではなく行列の列に対してのみ実行されます。
2次元配列の同義語はMatrix型です。 ただし、これは必要というよりむしろスタイルの利便性です。
マトリックス要素へのアクセスはインデックスによって実行されます。 たとえば、以前に作成されたマトリックスの場合
julia> x = rand(1:10, 2, 3) 2×3 Array{Int64,2}: 1 10 2 9 7 7
特定の要素をx [1、2] => 10として取得できます。したがって、列全体、たとえば2番目の列を取得します。
julia> x[:, 2] 2-element Array{Int64,1}: 10 7
または、2行目:
julia> x[2, :] 3-element Array{Int64,1}: 9 7 7
また、便利なselectdim関数もあります。この関数では、選択するディメンションの序数と、このディメンションの要素のインデックスを指定できます。 たとえば、1番目と3番目のインデックスを選択して、2番目の次元(列)で選択を行います。 このアプローチは、条件によっては行と列を切り替える必要がある場合に便利です。 ただし、これは、次元数が2を超える多次元の場合に当てはまります。
julia> selectdim(x, 2, [1, 3]) 2×2 view(::Array{Int64,2}, :, [1, 3]) with eltype Int64: 1 2 9 7
配列の統計処理のための関数
1次元配列の詳細
多次元配列
線形代数の関数と特殊な形式の行列
ファイルからテーブルを読み取るには、DelimitedFilesライブラリに実装されているreaddlm関数を使用します。 記録-writedlmを使用します。 これらの関数は、区切りファイルでの作業を提供します。その特定のケースはCSV形式です。
ドキュメントの例を使用して説明します。
julia> using DelimitedFiles julia> x = [1; 2; 3; 4]; julia> y = ["a"; "b"; "c"; "d"]; julia> open("delim_file.txt", "w") do io writedlm(io, [xy]) # end; julia> readdlm("delim_file.txt") # 4×2 Array{Any,2}: 1 "a" 2 "b" 3 "c" 4 "d"
この場合、テーブルにさまざまなタイプのデータが含まれていることに注意する必要があります。 そのため、ファイルを読み取ると、配列{Any、2}タイプのマトリックスが作成されます。
別の例は、同種のデータを含むテーブルの読み取りです。
julia> using DelimitedFiles julia> x = [1; 2; 3; 4]; julia> y = [5; 6; 7; 8]; julia> open("delim_file.txt", "w") do io writedlm(io, [xy]) # end; julia> readdlm("delim_file.txt", Int64) # Int64 4×2 Array{Int64,2}: 1 5 2 6 3 7 4 8 julia> readdlm("delim_file.txt", Float64) # Float64 4×2 Array{Float64,2}: 1.0 5.0 2.0 6.0 3.0 7.0 4.0 8.0
処理効率の観点から、このオプションは、データがコンパクトに表示されるため、望ましいです。 同時に、マトリックスで表されるテーブルの明示的な制限は、データの均一性の要件です。
ドキュメントの完全なreaddlm機能を確認することをお勧めします 追加オプションの中では 、ヘッダーの処理モード、行のスキップ、セルの処理機能などを指定できます 。
テーブルを読み取る別の方法はCSV.jlライブラリで、readdlmやwritedlmと比較して、このライブラリは、書き込みおよび読み取りのオプションを大幅に制御し、区切りファイルのデータをチェックします。 ただし、基本的な違いは、CSV.File関数の結果をDataFrame型に具体化できることです。
データフレーム
DataFramesライブラリは、テーブルの表示に焦点を当てたDataFrameデータ構造のサポートを提供します。 ここでのマトリックスとの基本的な違いは、各列が個別に保存され、各列に独自の名前があることです。 Juliaの場合、一般に列単位のストレージモードが自然であることを思い出してください。 また、ここでは1次元配列の特殊なケースがありますが、各列のタイプは個別にできるため、データ表現の速度と柔軟性の両方の点で最適なソリューションが得られます。
DataFrameの作成方法を見てみましょう。
任意のマトリックスをDataFrameに変換できます。
julia> using DataFrames julia> a = [1 2; 3 4; 5 6] 3×2 Array{Int64,2}: 1 2 3 4 5 6 julia> b = convert(DataFrame, a) 3×2 DataFrame │ Row │ x1 │ x2 │ │ │ Int64 │ Int64 │ ├─────┼───────┼───────┤ │ 1 │ 1 │ 2 │ │ 2 │ 3 │ 4 │ │ 3 │ 5 │ 6 │
convert関数は、データを指定された型に変換します。 したがって、DataFrameタイプの場合、変換関数のメソッドはDataFramesライブラリで定義されます(Juliaの用語によると、関数があり、さまざまな引数を持つさまざまな実装はメソッドと呼ばれます)。 マトリックスの列には、名前x1、x2が自動的に割り当てられることに注意してください。 つまり、列名を要求する場合、配列の形式で取得します。
julia> names(b) 2-element Array{Symbol,1}: :x1 :x2
また、名前はSymbol(Rubyの世界ではよく知られている)のような形式で表示されます。
DataFrameは直接作成できます-作成時に空または一部のデータを含みます。 例:
julia> df = DataFrame([collect(1:3), collect(4:6)], [:A, :B]) 3×2 DataFrame │ Row │ A │ B │ │ │ Int64 │ Int64 │ ├─────┼───────┼───────┤ │ 1 │ 1 │ 4 │ │ 2 │ 2 │ 5 │ │ 3 │ 3 │ 6 │
ここでは、列の値を持つ配列と、これらの列の名前を持つ配列を示します。 collect(1:3)という形式の構成体は、1から3までの反復子範囲を値の配列に変換します。
列へのアクセスは、名前とインデックスの両方で可能です。
既存のすべての行に値を書き込むことで、新しい列を追加するのは非常に簡単です。 たとえば、上記のdf、Score列を追加します。 これを行うには、次のように記述する必要があります。
julia> df[:Score] = 0.0 0.0 julia> df 3×3 DataFrame │ Row │ A │ B │ Score │ │ │ Int64 │ Int64 │ Float64 │ ├─────┼───────┼───────┼─────────┤ │ 1 │ 1 │ 4 │ 0.0 │ │ 2 │ 2 │ 5 │ 0.0 │ │ 3 │ 3 │ 6 │ 0.0 │
単純なマトリックスの場合と同様に、vcat、hcat関数を使用してDataFrameインスタンスを接着できます。 ただし、vcatは両方のテーブルの同じ列でのみ使用できます。 たとえば、次の関数を使用して、DataFrameを整列できます。
function merge_df(first::DataFrame, second::DataFrame)::DataFrame if (first == nothing) return second else names_first = names(first) names_second = names(second) sub_names = setdiff(names_first, names_second) second[sub_names] = 0 sub_names = setdiff(names_second, names_first) first[sub_names] = 0 vcat(second, first) end end
この関数は、列名の配列を取得します。 例のsetdiff(s1、s2)関数は、s2にないs1のすべての要素を検出します。 次に、DataFrameをこれらの要素に展開します。 vcatは2つのDataFrameを接着し、結果を返します。 最後の操作の結果は明らかなので、この場合にreturnを使用する必要はありません。
結果を確認できます。
julia> df1 = DataFrame(:A => collect(1:2)) 2×1 DataFrame │ Row │ A │ │ │ Int64 │ ├─────┼───────┤ │ 1 │ 1 │ │ 2 │ 2 │ julia> df2 = DataFrame(:B => collect(3:4)) 2×1 DataFrame │ Row │ B │ │ │ Int64 │ ├─────┼───────┤ │ 1 │ 3 │ │ 2 │ 4 │ julia> df3 = merge_df(df1, df2) 4×2 DataFrame │ Row │ B │ A │ │ │ Int64 │ Int64 │ ├─────┼───────┼───────┤ │ 1 │ 3 │ 0 │ │ 2 │ 4 │ 0 │ │ 3 │ 0 │ 1 │ │ 4 │ 0 │ 2 │
Juliaの命名規則に関しては、アンダースコアを使用することは慣習的ではありませんが、読みやすさが低下することに注意してください。 この実装では、元のDataFrameが変更されることもあまり良くありません。 ただし、それでも、この例は複数の列を整列するプロセスを説明するのに適しています。
結合関数を使用して、列内の共通の値で複数のDataFramesを結合することができます(たとえば、一般的なユーザーの識別子で異なる列を持つ2つのテーブルを接着します)。
DataFrameは、コンソールで表示するのに便利です。 出力のあらゆる方法: showマクロの使用、println関数の使用などにより、テーブルが読みやすい形式でコンソールに出力されます。 DataFrameが大きすぎる場合、開始行と終了行が表示されます。 ただし、head関数とtail関数を使用して、それぞれ明示的にheadとtailを要求できます。
DataFrameでは、指定された関数のデータのグループ化および集計関数が利用可能です。 返される内容には違いがあります。 これは、グループ化基準を満たすDataFrameを持つコレクション、または元の名前と集計関数の名前から列名が形成される単一のDataFrameになります。 本質的に、split-apply-combineスキームが実装されています。 詳細を見る
DataFramesパッケージの一部として使用可能なサンプルテーブルを含むドキュメントのサンプルを使用します。
julia> using DataFrames, CSV, Statistics julia> iris = CSV.read(joinpath(dirname(pathof(DataFrames)), "../test/data/iris.csv"));
groupby関数を使用してグループ化を実行します。 グループ化列の名前を指定し、GroupedDataFrameタイプの結果を取得します。これには、グループ化列の値によって収集された個々のDataFrameのコレクションが含まれます。
julia> species = groupby(iris, :Species) GroupedDataFrame with 3 groups based on key: :Species First Group: 50 rows │ Row │ SepalLength │ SepalWidth │ PetalLength │ PetalWidth │ Species │ │ │ Float64 │ Float64 │ Float64 │ Float64 │ String │ ├─────┼─────────────┼────────────┼─────────────┼────────────┼─────────┤ │ 1 │ 5.1 │ 3.5 │ 1.4 │ 0.2 │ setosa │ │ 2 │ 4.9 │ 3.0 │ 1.4 │ 0.2 │ setosa │ │ 3 │ 4.7 │ 3.2 │ 1.3 │ 0.2 │ setosa │
前述のcollect関数を使用して、結果を配列に変換できます。
julia> collect(species) 3-element Array{Any,1}: 50×5 SubDataFrame{Array{Int64,1}} │ Row │ SepalLength │ SepalWidth │ PetalLength │ PetalWidth │ Species │ │ │ Float64 │ Float64 │ Float64 │ Float64 │ String │ ├─────┼─────────────┼────────────┼─────────────┼────────────┼─────────┤ │ 1 │ 5.1 │ 3.5 │ 1.4 │ 0.2 │ setosa │ │ 2 │ 4.9 │ 3.0 │ 1.4 │ 0.2 │ setosa │ │ 3 │ 4.7 │ 3.2 │ 1.3 │ 0.2 │ setosa │ …
by関数を使用してグループ化します。 受信したDataFrameの列名と処理関数を指定します。 作業の最初の段階はgroupby関数に似ています-DataFrameコレクションを受け取ります。 このような各DataFrameについて、行の数をカウントして列Nに配置します。結果は単一のDataFrameに接着され、by関数の結果として返されます。
julia> by(iris, :Species, df -> DataFrame(N = size(df, 1))) 3×2 DataFrame │ Row │ Species │ N │ │ │ String⍰ │ Int64 │ ├─────┼────────────┼───────┤ │ 1 │ setosa │ 50 │ │ 2 │ versicolor │ 50 │ │ 3 │ virginica │ 50 │
さて、最後のオプションは集約関数です。 グループ化用の列と、残りの列の集計関数を指定します。 結果は、ソース列と集約関数の名前に代わって列名が形成されるDataFrameです。
julia> aggregate(iris, :Species, sum) 3×5 DataFrame │Row│Species │SepalLength_sum│SepalWidth_sum│PetalLength_sum│PetalWidth_sum│ │ │ String │ Float64 │ Float64 │ Float64 │ Float64 │ ├───┼──────────┼───────────────┼──────────────┼───────────────┼──────────────┤ │ 1 │setosa │250.3 │ 171.4 │ 73.1 │ 12.3 │ │ 2 │versicolor│296.8 │ 138.5 │ 213.0 │ 66.3 │ │ 3 │virginica │329.4 │ 148.7 │ 277.6 │ 101.3 │
colwise関数は、指定された関数をすべてまたは指定されたDataFrame列のみに適用します。
julia> colwise(mean, iris[1:4]) 4-element Array{Float64,1}: 5.843333333333335 3.057333333333334 3.7580000000000027 1.199333333333334
テーブルの概要を取得するための非常に便利な関数は、describeです。 使用例:
julia> describe(iris) 5×8 DataFrame │Row│ variable │mean │min │median│ max │nunique│nmissing│ eltype │ │ │ Symbol │Union… │Any │Union…│ Any │Union… │Int64 │DataType│ ├───┼───────────┼───────┼──────┼──────┼─────────┼───────┼────────┼────────┤ │ 1 │SepalLength│5.84333│ 4.3 │ 5.8 │ 7.9 │ │ 0 │ Float64│ │ 2 │SepalWidth │3.05733│ 2.0 │ 3.0 │ 4.4 │ │ 0 │ Float64│ │ 3 │PetalLength│3.758 │ 1.0 │ 4.35 │ 6.9 │ │ 0 │ Float64│ │ 4 │PetalWidth │1.19933│ 0.1 │ 1.3 │ 2.5 │ │ 0 │ Float64│ │ 5 │Species │ │setosa│ │virginica│ 3 │ 0 │ String │
マトリックスの場合と同様に、DataFrameのStatisticsモジュールで利用可能なすべての統計関数を使用できます。 https://docs.julialang.org/en/v1/stdlib/Statistics/index.htmlを参照してください
StatPlots.jlライブラリは、DataFrameをグラフィカルに表示するために使用されます。 もっと見るhttps://github.com/JuliaPlots/StatPlots.jl
このライブラリは、視覚化を簡素化するマクロのセットを実装しています。
julia> df = DataFrame(a = 1:10, b = 10 .* rand(10), c = 10 .* rand(10)) 10×3 DataFrame │ Row │ a │ b │ c │ │ │ Int64 │ Float64 │ Float64 │ ├─────┼───────┼─────────┼─────────┤ │ 1 │ 1 │ 0.73614 │ 7.11238 │ │ 2 │ 2 │ 5.5223 │ 1.42414 │ │ 3 │ 3 │ 3.5004 │ 2.11633 │ │ 4 │ 4 │ 1.34176 │ 7.54208 │ │ 5 │ 5 │ 8.52392 │ 2.98558 │ │ 6 │ 6 │ 4.47477 │ 6.36836 │ │ 7 │ 7 │ 8.48093 │ 6.59236 │ │ 8 │ 8 │ 5.3761 │ 2.5127 │ │ 9 │ 9 │ 3.55393 │ 9.2782 │ │ 10 │ 10 │ 3.50925 │ 7.07576 │ julia> @df df plot(:a, [:b :c], colour = [:red :blue])
最後の行では、@ dfはマクロ、dfはDataFrameを含む変数の名前です。
Query.jlは非常に便利なライブラリです。 Query.jlは、マクロのメカニズムと処理チャネルを使用して、特殊なクエリ言語を提供します。 例として、50歳以上の人とその子供の数のリストを取得します。
julia> using Query, DataFrames julia> df = DataFrame(name=["John", "Sally", "Kirk"], age=[23., 42., 59.], children=[3,5,2]) 3×3 DataFrame │ Row │ name │ age │ children │ │ │ String │ Float64 │ Int64 │ ├─────┼────────┼─────────┼──────────┤ │ 1 │ John │ 23.0 │ 3 │ │ 2 │ Sally │ 42.0 │ 5 │ │ 3 │ Kirk │ 59.0 │ 2 │ julia> x = @from i in df begin @where i.age>50 @select {i.name, i.children} @collect DataFrame end 1×2 DataFrame │ Row │ name │ children │ │ │ String │ Int64 │ ├─────┼────────┼──────────┤ │ 1 │ Kirk │ 2 │
または、チャネルのあるフォーム:
julia> using Query, DataFrames julia> df = DataFrame(name=["John", "Sally", "Kirk"], age=[23., 42., 59.], children=[3,5,2]); julia> x = df |> @query(i, begin @where i.age>50 @select {i.name, i.children} end) |> DataFrame 1×2 DataFrame │ Row │ name │ children │ │ │ String │ Int64 │ ├─────┼────────┼──────────┤ │ 1 │ Kirk │ 2 │
上記の両方の例は、dplyrまたはLINQと機能的に類似したクエリ言語の使用を示しています。 さらに、これらの言語はQuery.jlに限定されません。 DataFrameでこれらの言語を使用する方法の詳細については、こちらをご覧ください 。
最後の例では、| |演算子を使用します。 詳細をご覧ください 。
この演算子は、引数をその右側に示されている関数に置き換えます。 言い換えれば:
julia> [1:5;] |> x->x.^2 |> sum |> inv 0.01818181818181818
と同等:
julia> inv(sum( [1:5;] .^ 2 )) 0.01818181818181818
そして、私が注意したい最後のことは、前述のCSV.jlライブラリを使用して、セパレータ付きの出力フレームにDataFrameを書き込む機能です
julia> df = DataFrame(name=["John", "Sally", "Kirk"], age=[23., 42., 59.], children=[3,5,2]) 3×3 DataFrame │ Row │ name │ age │ children │ │ │ String │ Float64 │ Int64 │ ├─────┼────────┼─────────┼──────────┤ │ 1 │ John │ 23.0 │ 3 │ │ 2 │ Sally │ 42.0 │ 5 │ │ 3 │ Kirk │ 59.0 │ 2 │ julia> CSV.write("out.csv", df) "out.csv"
記録された結果を確認できます。
> cat out.csv name,age,children John,23.0,3 Sally,42.0,5 Kirk,59.0,2
おわりに
たとえば、ジュリアがRと同じくらい一般的なプログラミング言語になるかどうかを予測することは困難ですが、今年はすでに最も急速に成長しているプログラミング言語になりました。 昨年、今年、バージョン1.0のリリースとライブラリ関数の安定化の後、ごく少数の人しかそれを知らなかった場合、彼らはそれについて書き始めました。 そして、データの分析にジュリアを使用し始めなかった企業は、より機敏な子孫に取って代わられる完全な恐竜になるでしょう。
ジュリアは若いプログラミング言語です。 実際、パイロットプロジェクトの登場後、ジュリアのインフラストラクチャが実際の産業利用にどれだけ準備ができているかが明らかになります。 ジュリアの開発者は非常に野心的で、準備ができています。 いずれにせよ、ジュリアのシンプルだが厳格な構文は、今の学習にとって非常に魅力的なプログラミング言語です。 高いパフォーマンスにより、教育目的だけでなく、データ分析での実際の使用にも適したアルゴリズムを実装できます。 さまざまなプロジェクトで一貫してJuliaを試し始めます。