Как конвертировать между разными видами многомерных массивов в Julia

Я перехожу с python / numpy на julia. Меня действительно смущают многомерные массивы Джулии, и мне кажется, что есть некоторый дополнительный уровень сложности / проблем (по сравнению с numpy).

Существует различие между 1) векторами строк, 2) векторами-столбцами, 3) многомерными массивами и 4) вложенными массивами (= Массивы массивов). Это было бы хорошо (возможно, полезно для оптимизации производительности), если есть простой способ преобразования между ними. Но я не могу понять, как это сделать.

Простой пример: я просто пытаюсь создать двухмерную прямоугольную сетку точек и нанести их на график.


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
Нет векторов-строк или векторов-столбцов. Это просто одномерные массивы, двухмерные массивы, трехмерные массивы и т. Д. И никаких специальных "вложенных массивов". Просто у массивов могут быть любые члены, включая другие массивы. Откровенно говоря, он на много проще, более общий и последовательный, чем 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
Я имею в виду, что среди тех, с которыми вы работаете, нет векторов-строк или векторов-столбцов, а также нет специальных «вложенных массивов». Это просто одномерные массивы, двумерные массивы и т. Д. То, что вы называете векторами строк и векторами столбцов, - это просто двумерные массивы, которые имеют форму 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-массивом, а не 2D-массивом 100x1.

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 в одномерный вектор со 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

Не уверен, что вы хотите здесь произойти. Транспонирование возвращает ленивый тип данных из соображений производительности. Довольно аккуратно.

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