Документация Engee
Notebook

Распределение случайной величины в разных системах координат

В данном примере будут рассмотрены следующие темы:

  • генерация случайных чисел и массивов в Julia
  • использование распределений вероятности
  • важность правильного выбора распределения случайной величины в зависимости от постановки задачи.

Генерация случайных чисел по умолчанию

функция rand()

Для того чтобы сгенерировать случайное число, можно без подключения библиотек воспользоваться функцией rand() . Она вернёт случайное вещественное число в диапазоне [0,1)

In [ ]:
rand()
Out[0]:
0.8968475595278809

Если нужно создать матрицу $A^{i\times j \times k}$ определённого размера с элементами $a_{ijk}\in[0,1)$, то воспользуемся rand(i,j,k)

In [ ]:
rand(2,4,3)
Out[0]:
2×4×3 Array{Float64, 3}:
[:, :, 1] =
 0.105281  0.553249  0.571817  0.068993
 0.463789  0.510086  0.468581  0.599769

[:, :, 2] =
 0.845341  0.959642  0.488022  0.959579
 0.409156  0.49006   0.548231  0.899735

[:, :, 3] =
 0.957553  0.136052  0.885431  0.284849
 0.826566  0.92279   0.659209  0.384685

Если же в rand() передадать кортеж:

rand((2,4,3)),

то (2,4,3) будет множеством элементов, из которых выбирается случайный элемент

In [ ]:
rand((2,4,3))
Out[0]:
4
In [ ]:
rand(('a',3,"string",1+1im),2,3)
Out[0]:
2×3 Matrix{Any}:
   "string"  'a'    "string"
 1+1im       'a'  1+1im

Также в качестве множества элементов можно передавать словарь:

In [ ]:
rand(Dict("pi" => 3.14, "e" => 2.71), 3)
Out[0]:
3-element Vector{Pair{String, Float64}}:
 "e" => 2.71
 "e" => 2.71
 "e" => 2.71

Визуальный анализ распределения

Для того чтобы визуально оценить, по какому закону генерируются числа, воспользуемся библиотекой Plots. Для этого подключим её с помощью using Plots и после этого воспользуемся функцией histogram. Прописав gr() - отключим возможность интерактивного взаимодействия с графиками.

In [ ]:
using Plots             # подключаем библиотеку
gr()                    # отключаем интерактивное взаимодействие с графиком, т.к. точек много
histogram(rand(50000))  # строим гистограму 
title!("rand(50000)")   # подпись
Out[0]:

Как видно из гистограмы, распределение случайных чисел, сгенерированных при помощи rand(), является равномерным. Т.е. функция rand() генерирует величины со стандартным непрерывным равномерным распределением (СНРР)

Нормальное распределение

Также, без подключения других библиотек, в Julia можно генерировать случайные числа с нормальным распределением.

Функция randn() генерирует нормальное распределение чисел  с математическим ожиданием 0 и среднеквадратичным отклонением 1. (т.е. диапазон значений будет $(-\infty,\infty$)

In [ ]:
histogram(randn(50000),bims=0.005) # bims - ширина стобца, в который попадает точка
title!("randn(50000)")
Out[0]:

Важность выбора правильного распределения случайной величины

Рассмотрим такую задачу:

Создать набор точек внутри круга радиуса R=1 и с центром в (0,0), декартовы координаты которых равномерно распределены по осям абсцисс и ординат. Для этого

  1. Создадим набор точек, попадающих в квадрат с диагональю , проходящей через вершины (-1,1), (1,-1)
  2. Выберем те точки, которые которые попадают в единичный круг.

Шаг 1.

Для решения этого шага, необходимо помнить, что мы хотим получить равномерно распределённые значения X в диапазоне [-1,1]. А rand() - СНРР (т.е. [0,1)).

СНРР обладает свойством масштабирования: Если случайная величина $X \sim U[0,1]$ и $Y = a+(b-a)X$ , то $Y \sim U[\min(a,b),\max(a,b)]$

В нашем случае $a=-1$ и $b=1$ $\Rightarrow$

In [ ]:
POINTS_NUMBER = 3000
big_square_points = -1 .+ 2. .* rand(POINTS_NUMBER, 2)  # воспользовались свойством СНРР
Out[0]:
3000×2 Matrix{Float64}:
  0.0529451    -0.378398
 -0.926168     -0.595867
 -0.506066      0.586659
 -0.349772     -0.501738
 -0.199344     -0.742828
  0.869969      0.759997
  0.0879778    -0.111934
 -0.601932     -0.955655
  0.678965      0.0709128
 -0.873571     -0.671576
  0.161069      0.667479
  0.32568       0.848502
  0.14866      -0.0551039
  ⋮            
 -0.403644     -0.839549
 -0.103682     -0.11301
 -0.958197     -0.63964
  0.0131706     0.507728
  0.0394808     0.353285
 -0.948482     -0.794558
 -0.870387     -0.195976
  0.980847      0.716624
 -0.987589      0.0785435
  0.897117     -0.967298
  0.000805525  -0.625087
 -0.112197     -0.540335

Для того чтобы при построении графиков каждый раз не использовать одни и те же ключевые слова можно добавить PlotThemes и прописать необходимые слова "по умолчанию".

In [ ]:
Pkg.add("PlotThemes")
theme(:default, aspect_ratio=1, label = false, markersize=1)
scatter(big_square_points[:,1],big_square_points[:,2])
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`
Out[0]:

Шаг 2

Будем проходить по всем точками нашего квадрата и добавлять в массив те координаты, которые попадают в единичный круг.

In [ ]:
circle_points_x = Float64[]
circle_points_y = Float64[]
R = 1
for (x, y) in eachrow(big_square_points) # (в каждой строке big_square_points 2 элемента: x и y)
    if x^2 + y^2 < R^2
        push!(circle_points_x, x)
        push!(circle_points_y, y)
    end
end
scatter(circle_points_x,circle_points_y)
Out[0]:

Для решение нашей задачи более естественным подходом было бы использовать не декартовы координаты, а полярные.

Использование полярных координат

Тогда эта задача сведётся к одному, а не двум шагам:

Шаг 1 Выбрать $\rho \in [0,1]$ и $\alpha \in [0, 2\pi]$

In [ ]:
ρ = rand(POINTS_NUMBER)
α = 2π * rand(POINTS_NUMBER)
polar_original = scatter(ρ .* cos.(α), ρ .* sin.(α), aspect_ratio=1,markersize=1)  # перешли обратно в декартовы
Out[0]:

Также можно сразу посмотреть наши результаты, без перехода в декартовы координаты. Для этого предварительно отключим aspect_ratio=1 и укажем proj=:polar - полярное отображение.

In [ ]:
theme(:default)
scatter(α, ρ , proj=:polar, markersize=1)
Out[0]:

Можем заметить, что плотность точек возле начала координат выше, чем возле окружности.

Для наглядности вырежем из окружности вписанный квадрат.

Для этого будем использовать функцию zip():

In [ ]:
for i in zip(1:5,'a':'e',["🟥","🟩","🟦"])
    println(i)
end
# 4,5 и 'd','e' не напечатаются, так как коллекция цветов закончилась на 3 шаге
(1, 'a', "🟥")
(2, 'b', "🟩")
(3, 'c', "🟦")

Визуализация плотности точек

In [ ]:
small_square_points_x = Float64[]
small_square_points_y = Float64[]

for (x, y) in zip(circle_points_x, circle_points_y)
    if abs(x)  2 / 2 && abs(y)  2 / 2
        push!(small_square_points_x, x)
        push!(small_square_points_y, y)
    end
end
In [ ]:
small_square_points_ρ = Float64[]
small_square_points_α = Float64[]

for (x, y) in zip(ρ .* cos.(α), ρ .* sin.(α))
    if abs(x)  2 / 2 && abs(y)  2 / 2
        push!(small_square_points_ρ, x)
        push!(small_square_points_α, y)
    end
end

Чтобы показать сразу несколько графиков на одном полотне необходимо использовать layout и указать структуру их расположения (в нашем случае сетка $2 \times 2$. При помощи histogram2d сможем увидеть, в какой области большая плотность точек.

In [ ]:
p1 = scatter(   small_square_points_x, small_square_points_y,
                markersize=1, legend = false, aspect_ratio=1)
p2 = scatter(   small_square_points_ρ, small_square_points_α,
                markersize=1, legend = false, aspect_ratio=1)
p3 = histogram2d(small_square_points_x,small_square_points_y,
                 title="плотность в декартовых координатах", aspect_ratio=1)
p4 = histogram2d(small_square_points_ρ,small_square_points_α,
                 title="плотность в полярных координатах",aspect_ratio=1)

plot(p1,p2,p3,p4,layout=(2,2),size=(950,950))
Out[0]:

Функция repeat()

Рассмотрим функцию repeat()

In [ ]:
repeat([1:3;],2)
Out[0]:
6-element Vector{Int64}:
 1
 2
 3
 1
 2
 3
In [ ]:
repeat([1:3;],1,2)
Out[0]:
3×2 Matrix{Int64}:
 1  1
 2  2
 3  3
In [ ]:
repeat([1:3;],2,3)
Out[0]:
6×3 Matrix{Int64}:
 1  1  1
 2  2  2
 3  3  3
 1  1  1
 2  2  2
 3  3  3
In [ ]:
repeat([1:3;],inner=2)
Out[0]:
6-element Vector{Int64}:
 1
 1
 2
 2
 3
 3

Сравнение полярных и декартовых координат

Чтобы понять, почему применение полярных координат и случайное равномерное распределение по $\rho$ и $\alpha$ сработали таким, а не, возможно, ожидаемым образом, рассмотрим следующую картину. Возьмём не случайное, а фиксированное равномерное распределение:

In [ ]:
lngt = 12
a = repeat(range(0, stop=2pi,length=lngt),1,lngt)
r = repeat(range(0, stop=1, length=lngt),1,lngt)'
scatter(r .* cos.(a), r .* sin.(a),aspect_ratio=1, markersize=3)
Out[0]:
In [ ]:
x = repeat(range(0, stop=1, length=lngt),1,lngt)'
y = repeat(range(0, stop=1,length=lngt),lngt)
scatter(x, y,aspect_ratio=1)
Out[0]:

Как видим, плотность распределения точек в декартовых координатах является постоянной, а в полярных координатах плотность ваше возле начала координат.

Чтобы решить задачу нам нужно использовать такое распределение, которое будет с большей вероятностью ставить точку ближе к концу радиус-вектора, чем к началу. Предположим, что такое распределение должно быть линейным (чем ближе к концу, тем вероятнее). Оно называется треугольным.

Треугольное распределение

Чтобы воспользоваться треугольным распределением, подключим библиотеку Distributions. Данная библиотека имеет большой набор различных распределний (в частности, одномерных). Ознакомиться с этим набором можно в документации.

In [ ]:
using Pkg   
Pkg.add("Distributions")
using Distributions
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

Теперь создадим набор точек с треугольным распределением. Для этого в качестве первого аргумента функции rand() передадим TriangularDist(a,b,c) с параметрами a=0,b=1,c=1. triangular_distribution_pmf.png

In [ ]:
histogram(rand(TriangularDist(0,1,1),POINTS_NUMBER))
Out[0]:

Убедившись, что треугольное распределение работает так, как ожидалось,проверим, что его применение решит нашу задачу:

In [ ]:
ρ_triang = rand(TriangularDist(0,1,1), POINTS_NUMBER)
α_triang = rand(Uniform(0,2π), POINTS_NUMBER)    # Uniform(0,2π) - равномерное распределение от 0 до 2π
polar_triang = scatter(ρ_triang .* cos.(α_triang), ρ_triang .* sin.(α_triang),aspect_ratio=1, markersize=1)
plot(polar_original, polar_triang,layout=(1,2))
Out[0]:

В качестве интересного факта, покажем, как без подключения библиотеки Distributions можно было решить эту задачу, добавив всего два символа .√ :

In [ ]:
ρ_sqrt = .√rand(POINTS_NUMBER)
α_sqrt = 2π * rand(POINTS_NUMBER)
scatter(α_sqrt, ρ_sqrt, proj=:polar, markersize=1)
Out[0]:

Это справедливо из-за того, что:

Используя генератор равномерного распределения, можно получить генератор треугольного распределения: $$X = \begin{cases} a + \sqrt{U(b-a)(c-a)} & \text{ для } 0 < U < F(c) \\ & \\ b - \sqrt{(1-U)(b-a)(b-c)} & \text{ для } F(c) \le U < 1, \end{cases}$$ где $U$ - равномерно распределённое значение, а $F(c) = (c-a)/(b-a)$.

Подставив a=0,b=1,c=1, как раз и получим .√rand()

Заключение

В данном примере было показано, что язык Julia имеет как удобные встроенные функции, так и специализированные пакеты, позволяющие использовать специфические для области функции, взаимодействуя со стандартными.

Также были рассмотрены способы генерации массивов (repeat) и итерации по ним (zip и eachrow)