줄리아에서 그림 양식 만드는 법
개요
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)
데이터프레임을 넣어도 에러 없이 그림이 잘 그려지는 것을 확인할 수 있다. 꺾은 선의 투명도는 0.5고, 라벨 역시 데이터프레임에 있던 열이름을 그대로 따왔다.
timeevolution(df, legend = :left)
임의로 범례의 위치를 조정해 보았다. 앞서 f
를 정의할 때 legend
에 대해서는 어떤 언급도 없었음에도 잘 적용되는 것을 확인할 수 있다. 이는 timeevolution()
이 plot()
의 나머지 요소들을 상속받았기 때문이다.
환경
- OS: Windows
- julia: v1.6.3