줄리아에서 그림 양식 만드는 법 📂줄리아

줄리아에서 그림 양식 만드는 법

How to Make User Plot Template in julia

개요

RecipesBase.jl은 유저가 직접 새로운 그림의 양식을 만들게 해주는 패키지다. R 프로그래밍 언어에서 ggplot이 그러하듯 원래의 줄리아와는 또 다른 독자적인 문법1이 있다. 예제를 통해 익혀보자.

코드

using Plots
using DataFrames

df = DataFrame(x = 1:10, y = rand(10))
plot(df)

@userplot TimeEvolution
@recipe function f(te::TimeEvolution)
    df = te.args[1]
    linealpha --> 0.5

    column_names = names(df)
    for (column_index, column_name) ∈ enumerate(column_names)
        @series begin
            label --> column_name
            df[:,column_index]
        end
    end
end

timeevolution(df); png("1")
timeevolution(df, legend = :left); png("2")

우선 위 코드를 실행하면 다음과 같은 에러가 발생한다.

julia> df = DataFrame(x = 1:10, y = rand(10))
10×2 DataFrame
 Row │ x      y
     │ Int64  Float64   
─────┼──────────────────
   1 │     1  0.636532
   2 │     2  0.463764
   3 │     3  0.604559
   4 │     4  0.654089
   5 │     5  0.883409
   6 │     6  0.91667
   7 │     7  0.0609783
   8 │     8  0.602259
   9 │     9  0.460372
  10 │    10  0.479944

julia> plot(df)
ERROR: Cannot convert DataFrame to series data for plotting

이는 기본적으로 plot()이 데이터프레임을 받아서 그림을 그리는 방법이 정의되지 않았기 때문이다.

@userplot@recipe

@userplot TimeEvolution
@recipe function f(te::TimeEvolution)
    ...
end

예제에서는 시계열 데이터 그대로 꺾은 선 차트를 그려볼 것이다.

  • @userplot: 함수 plot()의 성질들을 상속받을 함수의 이름을 정한다. 여기서는 대소문자의 구분이 있지만, 완성되는 함수는 소문자만 사용할 수 있음에 주의하라.
  • @recipe: 구체적으로 그림의 양식을 지정해준다. 이 뒤에 오는 함수의 이름은 보통 관습적으로 f를 사용한다.

f(te::TimeEvolution)

df = te.args[1]
linealpha --> 0.5

column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
    @series begin
        label --> column_name
        df[:,column_index]
    end
end

이제 한 줄 한 줄 위 코드를 이해해보자.

df = te.args[1]

받은 인자 중 첫번째를 df로 볼 것이다. 이 예제에서는 넣어줄 인자가 데이터프레임으로 가정되어있으므로 그 약자인 df를 썼다. 이렇듯 f는 우리가 사용할 함수도 아니고, 그 인자 te 역시 직접적으로 사용되지 않음에 주의해야한다.

linealpha --> 0.5

이 그림에서 그려질 선들의 투명도를 0.5로 설정한다. 일반적인 그림에서 옵션을 주듯이 linealpha = 0.5이 아니라 -->으로 값을 준다는 것에 주의해야한다. 이는 보편적인 줄리아의 문법과 완전히 다르다.

column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
    ...
end

plot()이 2차원 배열을 곧장 그림으로 그려준다고 해도 그 라벨은 y1, y2와 같이 자동으로 주어진다. 이를 방지하기 위해 위와 같이 데이터프레임의 열이름을 받아와서 따로 라벨을 줄 것이다. enumerate() 함수를 통해 해당 인덱스와 열이름을 동시에 순회한다. 자세한 내용은 enumerate()에 대한 설명을 참고하자.

@series

for ...
    @series begin
        label --> column_name
        df[:,column_index]
    end
end

@series 매크로를 통해 반복적으로 그려질 데이터들과 그 양식을 정해준다. label --> column_name을 통해 열이름을 라벨로 주고, 가장 아래 행에 df[:,column_index]을 적음으로써 해당 열이름의 데이터들이 그려질 것이다. 이 때 그려질 데이터가 가장 아래 행에 있어야한다는 것에 주의하자.

결과

위의 결과로써 우리는 timeevolution() 혹은 timeevolution!() 으로 우리가 지정한 양식대로 그림을 그릴 수 있게 되었다.

timeevolution(df)

1.png

데이터프레임을 넣어도 에러 없이 그림이 잘 그려지는 것을 확인할 수 있다. 꺾은 선의 투명도는 0.5고, 라벨 역시 데이터프레임에 있던 열이름을 그대로 따왔다.

timeevolution(df, legend = :left)

2.png

임의로 범례의 위치를 조정해 보았다. 앞서 f를 정의할 때 legend에 대해서는 어떤 언급도 없었음에도 잘 적용되는 것을 확인할 수 있다. 이는 timeevolution()plot()의 나머지 요소들을 상속받았기 때문이다.

환경

  • OS: Windows
  • julia: v1.6.3

  1. https://docs.juliahub.com/RecipesBase/8e2Mm/1.0.1/syntax/ ↩︎

댓글