Как да конвертирате между различни видове многоизмерни масиви в Julia

Мигрирам от python/numpy към julia. Наистина съм объркан от многомерните масиви на Джулия и има чувството, че има някакво допълнително ниво на сложност / караница (в сравнение с numpy).

Има разлика между 1)редове-вектори 2)колони-вектори, 3)многоизмерни масиви и 4)вложени масиви (=Масиви от масиви). Това би било добре (може би полезно за оптимизиране на производителността), ако приемем, че има лесен начин за конвертиране между тях. Но не мога да разбера как да го направя.

Прост пример: Просто се опитвам да генерирам 2D правоъгълна мрежа от точки и да ги начертая


ps = [ [ix*0.1 iy*0.1] for ix=1:10, iy=1:10 ]
# 10×10 Array{Array{Float64,2},2}:
# Oh, this is nested array? I wand just simple 3D array 10x10x2

scatter( ps[:,:,1], ps[:,:,2], markersize = 2, markerstrokewidth = 0, aspect_ratio=:equal )
# ERROR: BoundsError: attempt to access 10×10 Array{Array{Float64,2},2} at index [Base.Slice(Base.OneTo(10)), Base.Slice(Base.OneTo(10)), 2]

sh = size(ps)
# (10,10)

ps = reshape( ps, ( sh[1]*sh[2],2) )
# ERROR: DimensionMismatch("new dimensions (100, 2) must be consistent with  array size 100")
# Oh dear :(

ps = reshape( ps, ( sh[1]*sh[2],:) )
# 100×1 Array{Array{Float64,2},2}

xs = ps[:,1]
# 100-element Array{Array{Float64,2},1}
# ??? WTF? ... this arrays looks like whole 'ps' 
ys = ps[:,2] 
# ERROR: BoundsError: attempt to access 100×1 Array{Array{Float64,2},2} at index [Base.Slice(Base.OneTo(100)), 2]

xs = ps[:][1]
# 1×2 Array{Float64,2}: 
#  0.1  0.1
#  But I want all xs  (ps[:,1]), not (ps[1,:]) 

# Let's try some broadcasting
xs = ps.[1]
# ERROR: syntax: invalid syntax "ps.[1]"
xs = .ps[1]
# ERROR: syntax: invalid identifier name "."


# Perhaps transpose will help?
ps_ = ps'   #' stackoverflow syntax highlighting for Julia is broken ?
# 1×100 LinearAlgebra.Adjoint{LinearAlgebra.Adjoint{Float64,Array{Float64,2}},Array{Array{Float64,2},2}}:
# OMG! ... That is even worse

scatter( ps[:,1], ps[:,2], markersize = 2, markerstrokewidth = 0, aspect_ratio=:equal )
# Nope

Добре, това някак работи. Но все пак трябва да разбера как да конвертирам между различните форми на масиви по-горе

using Plots
ps = [ [ix*0.1 iy*0.1] for ix=1:10, iy=1:10 ]
ps = vcat(ps...)
xs = ps[:,1]
ys = ps[:,2]
scatter( xs, ys, markersize = 2, markerstrokewidth = 0, aspect_ratio=:equal )

РЕДАКТИРАНЕ:

Може би би било добре да изброя някои уроци, където търсих отговора, преди да попитам:


person Prokop Hapala    schedule 16.10.2019    source източник
comment
Ако искате да излъчвате индексиране, arr.[index] не работи, но getindex.(arr, index) работи. Квадратните скоби са просто синтактична захар за getindex повикване.   -  person crstnbr    schedule 16.10.2019
comment
Няма вектори на редове или вектори на колони. Това са само 1d масиви, 2d масиви, 3d масиви и т.н. И няма специални „вложени масиви“. Просто масивите могат да имат всякакви членове, включително други масиви. Честно казано, той е много по-прост, по-общ и последователен от Python/numpy.   -  person DNF    schedule 17.10.2019
comment
DNF › колона срещу ред има голямо значение при използване на излъчване. Следвах този урок, за да създам 2D мрежа от точки ( julia.guide/broadcasting) и тя (като @. floor(Int, sqrt(a) + sqrt(b))) не работеше, докато не осъзнах, че има значение дали пиша там [2 3 4 5] или [2, 3, 4, 5]. И разбирането като [ [ix*0.1 iy*0.1] for ix=1:10, iy=1:10 ] винаги произвежда колонен вектор.   -  person Prokop Hapala    schedule 17.10.2019
comment
Това, което имам предвид, е, че няма типове векторни редове или векторни колони сред тези, с които работите, и няма специални „вложени масиви“. Това са само 1D масиви, 2D масиви и т.н. Това, което наричате вектори на редове и вектори на колони, са просто 2D масиви, които случайно имат форма 1xN или Nx1. Така че изглежда странно да се каже, че е сложно поради разликите, които изброявате, когато тези разлики не са реални (с изключение, разбира се, че 2D и по-високо D масивите са различни).   -  person DNF    schedule 17.10.2019
comment
Що се отнася до последния ви пример, [2 3 4 5] създава 2D масив, докато [2, 3, 4, 5] създава 1D масив.   -  person DNF    schedule 17.10.2019


Отговори (3)


Джулия работи в колонен стил, така че основният вектор е колонен вектор. За да преобразувате вектор на колона във вектор на ред, използвайте permutedims(colvec). За да преобразувате вектор на ред във вектор на колона, използвайте permutedims(rowvec).

julia> colvec = [1, 2, 3]
3-element Array{Int64,1}:
 1
 2
 3

julia> rowvec = permutedims(colvec)
1×3 Array{Int64,2}:
 1  2  3

julia> permutedims(rowvec)
3×1 Array{Int64,2}:
 1
 2
 3

За да преобразувате матрица (или който и да е двуизмерен масив) във вектор колона, използвайте vec. Тъй като Джулия съхранява двумерни масиви по колони, това ще обхване всяка колона на свой ред. Имайте предвид, че размерите на 2D масива са показани като <rows>x<cols> Array{<type>,2}.

julia> matrix = [1 4 
                 2 5 
                 3 6]
3×2 Array{Int64,2}:
 1  4
 2  5
 3  6  

julia> colvec = vec(matrix)
6-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6

За да преобразувате този colvec обратно в оригиналния масив, трябва да знаете размерите на този оригинален масив. ndims(x) брои размерите на x, а size(x) дава броя на елементите във всяко измерение на x.

julia> reshape(colvec, size(matrix))
3×2 Array{Int64,2}:
 1  4
 2  5
 3  6

Можете да транспонирате записите с permutedims(matrix).

julia> matrix
3×2 Array{Int64,2}:
 1  4
 2  5
 3  6

julia> permutedims(matrix)
2×3 Array{Int64,2}:
 1  2  3
 4  5  6

Същите принципи се прилагат и за по-високомерни масиви.

array = reshape(collect(1:12), (3, 2, 2))
3×2×2 Array{Int64,3}:
[:, :, 1] =
 1  4
 2  5
 3  6

[:, :, 2] =
 7  10
 8  11
 9  12

julia> vec(array)
12-element Array{Int64,1}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12

За работа с вложени масиви предлагам да използвате RecursiveArrayTools.jl.

person Jeffrey Sarnoff    schedule 16.10.2019

Има доста неща в кода по-горе, но просто се фокусираме върху вашата отправна точка и очаквания резултат:

Защо вложен масив?

Вашето разбиране създава масива [ix*0.1 iy*0.1] за всяка комбинация от ix и iy, така че бих казал, че изрично сте го поискали.

Вероятно има някои хитри начини или да направите това с фантастично разбиране, или по някакъв начин да изравните вложения масив, но в случаи като този обичам да бъда ясен за това, което се опитвам да постигна:

ps = zeros(10,10,2) # 10x10x2 Array{Float64,3}
for ix = 1:10, iy = 1:10
        ps[ix, iy, :] = [ix*0.1 iy*0.1]
end

Ако става въпрос за едноредов, можете да обмислите създаването на двата масива 10x10 в разбиранията и след това да ги свържете в трето измерение:

ps = cat([ix*0.1 for ix=1:10, iy=1:10], [iy*0.1 for ix=1:10, iy=1:10], dims = 3)
person Nils Gudat    schedule 16.10.2019
comment
добре, благодаря, но тъй като това са неща, които ще пиша хиляди пъти във всеки един скрипт, търся най-сбитите едноредови думи или идиоми. - person Prokop Hapala; 16.10.2019

Други са дали отговори, които помагат за разрешаването на вашия проблем, така че предпочитам просто да прегледам вашия пример и да се опитам да обясня какво се случва. Изглеждате доста разочарован, което не е необичайно, когато изучавате нов език, но повечето от оплакванията ви изглежда са, защото Джулия е последователна.

ps = [ [ix*0.1 iy*0.1] for ix=1:10, iy=1:10 ]
# 10×10 Array{Array{Float64,2},2}:
# Oh, this is nested array? I wand just simple 3D array 10x10x2

Тук създавате масив 10x10, където всеки елемент е матрица 1x2. Това е, което искаш, Джулия не е трудна или неясна, просто е последователна и ясна.

scatter( ps[:,:,1], ps[:,:,2], markersize = 2, markerstrokewidth = 0, aspect_ratio=:equal )
# ERROR: BoundsError: attempt to access 10×10 Array{Array{Float64,2},2} at index [Base.Slice(Base.OneTo(10)), Base.Slice(Base.OneTo(10)), 2]

Имате 2D масив, така че не можете да индексирате в него с 3 индекса.

sh = size(ps)
# (10,10)

ps = reshape( ps, ( sh[1]*sh[2],2) )
# ERROR: DimensionMismatch("new dimensions (100, 2) must be consistent with  array size 100")
# Oh dear :(

Имате масив 10x10 и се опитвате да го преоформите в масив 100x2. Новият масив ще има 200 елемента, което е два пъти повече от оригинала, така че това не може да работи.

ps = reshape( ps, ( sh[1]*sh[2],:) )
# 100×1 Array{Array{Float64,2},2}

Тук го преформатирате в масив 100x1, това е добре.

xs = ps[:,1]
# 100-element Array{Array{Float64,2},1}
# ??? WTF? ... this arrays looks like whole 'ps' 

И сега вие питате за първата (и единствена) колона от новата, преработена ps. Така че, естествено, получавате всички данни. Забележете, че xs сега е 1D масив, а не 100x1 2D масив.

ys = ps[:,2] 
# ERROR: BoundsError: attempt to access 100×1 Array{Array{Float64,2},2} at index [Base.Slice(Base.OneTo(100)), 2]

Искате втората колона от масив 100x1.

xs = ps[:][1]
# 1×2 Array{Float64,2}: 
#  0.1  0.1
#  But I want all xs  (ps[:,1]), not (ps[1,:]) 

ps[:] превръща ps в 1D вектор със 100 елемента и след това питате за първия елемент от това. Струва ми се очаквано поведение.

# Let's try some broadcasting
xs = ps.[1]
# ERROR: syntax: invalid syntax "ps.[1]"

Да, това не работи и не е неразумно да се очаква, че може, но това е възможна бъдеща функция. Може би търсите first.(ps), което чете първия елемент от всеки елемент на ps. По същия начин last.(ps) чете последния елемент от всеки елемент от ps.

xs = .ps[1]
# ERROR: syntax: invalid identifier name "."

Това не е валиден синтаксис. Синтаксисът на точка работи само с функции и оператори.

# Perhaps transpose will help?
ps_ = ps'   #' stackoverflow syntax highlighting for Julia is broken ?
# 1×100 LinearAlgebra.Adjoint{LinearAlgebra.Adjoint{Float64,Array{Float64,2}},Array{Array{Float64,2},2}}:
# OMG! ... That is even worse

Не съм сигурен какво искаш да се случи тук. Transpose връща мързелив тип данни от съображения за производителност. Доста е спретнато.

scatter( ps[:,1], ps[:,2], markersize = 2, markerstrokewidth = 0, aspect_ratio=:equal )
# Nope

Доколкото си спомням, променихте ps в масив 100x1, така че ps[:,2] не може да работи.

person DNF    schedule 17.10.2019
comment
DNF - не, не бях разочарован :) Съжалявам, че коментарите изглеждат така. Просто исках да направя текста по-жив, като карикатура. Това, което исках да изразя, е, че тази на пръв поглед проста операция не е толкова ясна, като използвате всички инструменти, които обикновено срещате в уроци/шитове. Разбирам разликата между масив 10x10x2 и масив 10x10 от масиви 2x1. Някаква удобна функция до arr.[i] може да бъде напр. reshape( ps, ( sh[1]*sh[2],2), deep=True ), което би разопаковало/преопаковало вложения масив. - person Prokop Hapala; 17.10.2019