jl4R: une vie polyamoureuse entre R et Julia
R(ulia) Drouilhet
https://cqls.dyndoc.fr/jl4R/talk
https://cqls.dyndoc.fr/jl4R/talkjl4R (obsolète) remplacé par Rulia, voir https://cqls.dyndoc.fr/Rulia/talk►Introduction
►Pourquoi j'aime R et julia?
R: (single) dispatching, keyword arguments, simplicité, metaprgrammation, gestionnaire packages uniquejulia: quasiment la même chose + performance (multiple dispatching, J.I.T. compilation, arbre des Types)
Le langage
R (mes premiers amours de jeunesse 😂)- (single) dispatching (S3): comme une alternative (équivalente) à la programmation OOP, mettant en avant action/verbe
- keyword arguments: paramètres nommés avec valeur par défaut équivalent à plusieurs fonctions en une seule fonction
- simplicité: peu de structures de données et notion d'attribut
- metaprogrammation: expressif puisque de type LISP (le code c'est de la donnée)
- gestionnaire packages: unique
Le langage
julia : digne successeur de R, un UNIQUE langage hybride R/C++- le triptyque:
- multiple dispatching : action différente selon signature (pas seulement le premier argument)
- Just In Time (J.I.T.) compilation
- arbre des Types (toute variable a un type)
- performance: rétablissement (direct et indirect) de la boucle for (à la
C++) - expressivité:
- metaprogrammation via système de macro
- keyword arguments (hors signature)
- gestionnaires packages: unique (basé sur le package
Pkg.jl)
►Pourquoi le package jl4R (julia pour R)
- une évidence (pour moi):
juliaun UNIQUE langage combinant expressivité duRet performance duC++ jl4R(alternative àJuliaCall) pour créer packageRde type "wrapper" de packagejuliasans dépendance àRcppetRCall.jl(contrairement àJuliaCall)
Historique de
jl4R- créé il y 10 ans environ (
JuliaCallil y a 7ans), usage limité à communication entrevector RetArray julia - origine "remplacer
Rcppparjulia" :-
création de
VirtualAgeModels.jl(packagejulia) inspirée parVAM(packageRbasé surRcpp) -
une évidence:
juliaun UNIQUE langage combinant expressivité duRet performance duC++
-
création de
- intensification du développement du package
jl4R:-
pour créer package
Rde type "wrapper" de packagejulia -
sans dépendance à
RcppetRCall.jl(contrairement àJuliaCall) -
utilisateur
Rdu "wrapper" pas besoin de connaîtrejulia(comme pourRcpp)
-
pour créer package
Objectif final de
jl4R : VirtualAgeModelsJL en simulationjulia> using VirtualAgeModels julia> m_sim = @vam(Time & Type ~ (ARA∞(0.4) | Weibull(.001,2.5))); julia> df = rand(m_sim,100); julia> first(df,5) 5×2 DataFrame Row │ Time Type │ Float64 Int64 ─────┼───────────────── 1 │ 5.87783 -1 2 │ 19.9294 -1 3 │ 25.5999 -1 4 │ 30.5028 -1 5 │ 30.5875 -1
R> require(VirtualAgeModelsJL) R> m_sim = vam(Time & Type ~ (ARAInf(.4) | Weibull(.001,2.5))) R> df = rand(m_sim,100) R> head(df) Time Type 1 11.45393 -1 2 26.78059 -1 3 27.75529 -1 4 36.41756 -1 5 41.22767 -1 6 54.27561 -1
Objectif final de
jl4R : VirtualAgeModelsJL en estimation MLEjulia> using VirtualAgeModels julia> m_mle = @vam(Time & Type ~ (ARA∞(0.4) | Weibull(.001,2.5))); julia> mle(m_mle, df); julia> params(m_mle) 3-element Vector{Float64}: 0.001 2.6102623440166473 0.33550975279136264
R> require(VirtualAgeModelsJL) R> m_mle = vam(Time & Type ~ (ARAInf(.4) | Weibull(.001,2.5))) R> mle(m_mle, data = df) 3-element Vector{Float64}: 0.001 2.425609861033199 0.3720243399410744 R> params(m_mle) 3-element Vector{Float64}: 0.001 2.425609861033199 0.3720243399410744
►jl4R (julia for R)
►Utilisation de jl4R
- Workflow
- Création fonction(s)
julia - Appel de fonction(s)
juliaenR - 3 modes d'utilisation:
- mode "unsafe"
jlvalue - mode "safe"
jl - mode "safe" plus expressif
jl+
- mode "unsafe"
- Création fonction(s)
Utilisation du package
jl4R- Worflow étape 1: Création fonction(s)
juliavia au choix :-
création package julia (puis dans
R,jlusing(_package_)) -
création fichier
julia(puis dansR,jlinclude(_fichier_)) -
création "inline" (à la
Rcpp) directement dansR(dansR,jl(_julia_code_))
-
création package julia (puis dans
- Worflow étape 2: Appel fonction(s)
juliadansR-
conversion
R -> julia(viajl(_objet_R_)oujlvalue(_objet_R_) -
appel fonction
juliadansR(viajl(_julia_function_)(...)) -
conversion
julia -> R(viaR(_jlvalue_object_))
-
conversion
Les 3 modes d'utilisation de
jl4R- mode
jlvalue("unsafe"):-
conversions
jlvalue()et fonctionsjlvalue_...(): -
moins sûr car proche des
API CduRetjulia - plutôt à un utiliser en mode développeur (on sait ce l'on fait)
-
conversions
- mode
jl(basique mais "safe") :-
a priori "safe" car utilisation système d'exception (
try/catch) -
principales fonctions:
jleval()etjlcall()
-
a priori "safe" car utilisation système d'exception (
- mode
jl+(plus élaboré et "safe") :-
uniquement: un environnement
jlet une fonctionjl() -
objectifs avoués :
-
format le plus similaire au code original
julia -
pas (ou peu) de chaînes de caractères pour appel de fonction
julia
-
format le plus similaire au code original
-
uniquement: un environnement
1. Code
julia (mode jl+)julia> [1,3,4] 3-element Vector{Int64}: 1 3 4 julia> VERSION v"1.9.3"
R> jl(`[1,3,4]`) 3-element Vector{Int64}: 1 3 4 R> jl(VERSION) # ou jl(`VERSION`) v"1.9.3"
1. Code
julia (mode jl)julia> [1,3,4] 3-element Vector{Int64}: 1 3 4 julia> VERSION v"1.9.3"
R> jleval("[1,3,4]") 3-element Vector{Int64}: 1 3 4 R> jleval("VERSION") v"1.9.3"
1. Code
julia (mode jlvalue)julia> [1,3,4] 3-element Vector{Int64}: 1 3 4 julia> VERSION v"1.9.3"
R> jlvalue_eval("[1,3,4]") 3-element Vector{Int64}: 1 3 4 R> jlvalue_eval("VERSION") v"1.9.3"
2. Code
julia multilignes (mode jl+)julia> f(x,y) = x + y f (generic function with 1 method) julia> (f(2,3), f(1.0,3)) (5, 4.0)
R> jl(` + f(x,y) = x + y + (f(2,3), f(1.0,3)) + `) (5, 4.0)
2. Code
julia multilignes (mode jl)julia> f(x,y) = x + y f (generic function with 1 method) julia> (f(2,3), f(1.0,3)) (5, 4.0)
R> jleval(" + f(x,y) = x + y + (f(2,3), f(1.0,3)) + ") (5, 4.0)
2. Code
julia multilignes (mode jlvalue)julia> f(x,y) = x + y f (generic function with 1 method) julia> (f(2,3), f(1.0,3)) (5, 4.0)
R> jlvalue_eval(" + f(x,y) = x + y + (f(2,3), f(1.0,3)) + ") (5, 4.0)
3. Conversion (basique)
R -> julia (mode jl+)julia> t = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof.(t) (Bool, Int64, Float64, String)
R> jl(TRUE) true R> jl(1L) 1 R> jl(1) 1.0 R> jl("1.0") "1.0"
3. Conversion (basique)
R -> julia (mode jlvalue)julia> t = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof.(t) (Bool, Int64, Float64, String)
R> jlvalue(TRUE) true R> jlvalue(1L) 1 R> jlvalue(1) 1.0 R> jlvalue("1.0") "1.0"
4. Conversion (vecteur)
R -> julia (mode jl+)julia> a = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof(a) Tuple{Bool, Int64, Float64, String}
R> jl(c(TRUE, 1L, 1, "1.0")) 4-element Vector{String}: "TRUE" "1" "1" "1.0" R> jl(list(TRUE, 1L, 1, "1.0")) (true, 1, 1.0, "1.0")
4. Conversion (vecteur)
R -> julia (mode jlvalue)julia> a = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof(a) Tuple{Bool, Int64, Float64, String}
R> jlvalue(c(TRUE, 1L, 1, "1.0")) 4-element Vector{String}: "TRUE" "1" "1" "1.0" R> jlvalue(list(TRUE, 1L, 1, "1.0")) (true, 1, 1.0, "1.0")
5. Variables
julia dans R (mode jl+)julia> a = [true, 1, 1.0, "1.0"] 4-element Vector{Any}: true 1 1.0 "1.0" julia> typeof(a) Vector{Any} (alias for Array{Any, 1}) julia> b = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof(b) Tuple{Bool, Int64, Float64, String}
R> ## Native julia objects R> jl$a <- jl(`[true, 1, 1.0, "1.0"]`) R> jl$a 4-element Vector{Any}: true 1 1.0 "1.0" R> jl$b <- jl(`(true, 1, 1.0, "1.0")`) R> jl$b (true, 1, 1.0, "1.0") R> ## with conversion R -> julia R> jl$b2 <- list(TRUE, 1L, 1, "1.0") R> jl$b2 (true, 1, 1.0, "1.0")
5. Variables
julia dans R (mode jl)julia> a = [true, 1, 1.0, "1.0"] 4-element Vector{Any}: true 1 1.0 "1.0" julia> typeof(a) Vector{Any} (alias for Array{Any, 1}) julia> b = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof(b) Tuple{Bool, Int64, Float64, String}
R> jleval('a =[true, 1, 1.0, "1.0"]') 4-element Vector{Any}: true 1 1.0 "1.0" R> jleval('a') 4-element Vector{Any}: true 1 1.0 "1.0" R> jleval('b = (true, 1, 1.0, "1.0")') (true, 1, 1.0, "1.0") R> jleval('b') (true, 1, 1.0, "1.0") R> ## error below don't crash R> jleval('b = (true, 1, 1.0, "1.0"') Julia Exception: ErrorException
5. Variables
julia dans R (mode jlvalue)julia> a = [true, 1, 1.0, "1.0"] 4-element Vector{Any}: true 1 1.0 "1.0" julia> typeof(a) Vector{Any} (alias for Array{Any, 1}) julia> b = (true, 1, 1.0, "1.0") (true, 1, 1.0, "1.0") julia> typeof(b) Tuple{Bool, Int64, Float64, String}
R> jlvalue_eval('a =[true, 1, 1.0, "1.0"]') 4-element Vector{Any}: true 1 1.0 "1.0" R> jlvalue_eval('a') 4-element Vector{Any}: true 1 1.0 "1.0" R> jlvalue_eval('b = (true, 1, 1.0, "1.0")') (true, 1, 1.0, "1.0") R> jlvalue_eval('b') (true, 1, 1.0, "1.0") R> ## error below will crash badly R> # jlvalue_eval('b = (true, 1, 1.0, "1.0"')
6. Appel fonction
julia dans R (mode jl+)julia> sum sum (generic function with 11 methods) julia> typeof(sum) typeof(sum) (singleton type of function sum, subtype of Function) julia> sum([1,3,2]) 6 julia> sum([1,3,2], init=4) 10 julia> isa(sum,Function) true julia> sum isa Function true
R> jl(sum) sum (generic function with 11 methods) R> class(jl(sum)) [1] "jlfunction" R> jl(typeof)(sum) typeof(sum) (singleton type of function sum, subtype of Function) R> jl(sum)(`[1,3,2]`) 6 R> jl(sum)(c(1,3,2)) 6.0 R> jl(sum)(c(1,3,2), init=4) 10.0 R> jl(isa)( `sum`,`Function`) true R> jl(isa)(sum,Function) true
6. Appel fonction
julia dans R (mode jl)julia> sum sum (generic function with 11 methods) julia> typeof(sum) typeof(sum) (singleton type of function sum, subtype of Function) julia> sum([1,3,2]) 6 julia> sum([1,3,2], init=4) 10 julia> isa(sum,Function) true julia> sum isa Function true
R> jleval("sum") sum (generic function with 11 methods) R> jleval("typeof(sum)") typeof(sum) (singleton type of function sum, subtype of Function) R> jlcall("sum", jleval("[1,3,2]")) 6 R> jlcall("sum", c(1,3,2), init=4) 10.0 R> jlcall("isa", jleval("sum"),jleval("Function")) true R> jleval("sum isa Function") true
6. Appel fonction
julia dans R (mode jlvalue)julia> sum sum (generic function with 11 methods) julia> typeof(sum) typeof(sum) (singleton type of function sum, subtype of Function) julia> sum([1,3,2]) 6 julia> sum([1,3,2], init=4) 10 julia> isa(sum,Function) true julia> sum isa Function true
R> jlvalue_eval("sum") sum (generic function with 11 methods) R> jlvalue_eval("typeof(sum)") typeof(sum) (singleton type of function sum, subtype of Function) R> jlvalue_call("sum",jlvalue_eval("[1,3,2]")) 6 R> ## Not possible: jlvalue_call("sum", jlvalue([1,3,2]), init=4)") R> jlvalue_eval("isa(sum,Function)") true R> jlvalue_eval("sum isa Function") true
7. Conversion
julia -> RR> jl$a <- jl(`[1,3,2]`) R> R(jl$a) [1] 1 3 2 R> jl(sum)(jl$a) 6 R> jl(sum)(a) 6 R> R(jl(sum)(jl$a)) [1] 6 R> R(jlcall("sum", c(1,3,2), init=4)) [1] 10
►jl4R: un peu plus loin avec mode jl+
- metapgrommation
Rpermet au modejl+un format assez similaire au code originaljulia - pas (ou peu) de chaînes de caractères pour appel de fonction
julia
1. Mode
Règle 1: jl+: fonctionnement de jl()jl(`...`): si argument de jl() est de type name alors exécution de code en julia avec résultat retourné de classe jlvalue
R> jl(`10`) 10 R> jl(`'1'`) '1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit) R> jl(`"1"`) "1" R> jl(`(a=[1,"toto", '1'], b=12//13)`) (a = Any[1, "toto", '1'], b = 12//13) R> class(jl(`'1'`)) [1] "Char" "jlvalue" R> class(jl(`"1"`)) [1] "String" "Struct" "jlvalue"
1. Mode
Règle 2: si argument de jl+: fonctionnement de jl()jl() est de classe jlvalue, cela retourne la jlvalue
R> jl(`10`) 10 R> jl(jl(`10`)) 10 R> jlval10 <- jl(`10`) R> class(jlval10) [1] "Int64" "jlvalue" R> jl(jlval10) 10 R> R(jlval10) [1] 10
1. Mode
Règle 3: si argument de jl+: fonctionnement de jl()jl() est un objet R alors il est converti en julia (si possible, en fait redirection à jlvalue())
R> c(jl(10), jl(10L), jl("10"), jl(TRUE)) ## ou c(jlvalue(10), jlvalue(10L), jlvalue("10"), jlvalue(TRUE)) [[1]] 10.0 [[2]] 10 [[3]] "10" [[4]] true
1. Mode
Règle 4: variable jl+: fonctionnement de jl()R comme argument de jl() prioritaire par rapport à variable julia de même nom
R> ## a prioritaire par rapport à jl$a R> jl$a <- jl(10) R> a <- 12 R> jl(a) 12.0 R> jl(jl$a) 10.0 R> ## jl$b appelé R> jl$b <- 11 R> b Erreur dans evalq({ : objet 'b' introuvable R> jl(b) 11.0 R> jl(jl$b) 11.0
1. Mode
Règle 5: si argument de jl+: fonctionnement de jl()jl() est une expression julia, l'objet julia est retourné
R> jl(sin) sin (generic function with 21 methods) R> jl(Array) Array R> jl(Vector) Vector (alias for Array{T, 1} where T) R> jl(jl(Vector)) Vector (alias for Array{T, 1} where T) R> class(jl(Vector)) [1] "UnionAll" "Struct" "jlvalue" R> ## jl(Vector{Float64}) pas possible car Vector{Float64} pas une expression R R> jl(`Vector{Float64}`) Vector{Float64} (alias for Array{Float64, 1})
1. Mode
Règle 6:
jl+: fonctionnement de jl()-
si
jl()retourne une fonctionjuliaelle est convertie enjlfunctionqui permet de s'exécuter enR -
à l'appel de la
jlfunction,jl()est appliquée à tous ses arguments au préalable
R> jl(sin) sin (generic function with 21 methods) R> class(jl(sin)) [1] "typeof(sin)" "jlfunction" R> jl(sin)(13) # conversion R -> julia 0.4201670368266409 R> jl(sin)(`13`) # pas de conversion 0.4201670368266409 R> jl(`sqrt ∘ +`) sqrt ∘ (+) R> class(jl(`sqrt ∘ +`)) [1] "ComposedFunction{typeof(sqrt), typeof(+)}" "jlfunction" R> jl(`sqrt ∘ +`)(3, 6) 3.0
1. Mode
Règle 7: si argument de jl+: fonctionnement de jl()jl() est un name avec une expression julia mal formée, une jlexception est retournée
R> jl(sin)(`[1,2]`) Julia Exception: MethodError R> class(jl(sin)(`[1,2]`)) [1] "MethodError" "jlexception"
2. Broadcasting avec
jl4Rjulia> sin.([1,4,2]) 3-element Vector{Float64}: 0.8414709848078965 -0.7568024953079282 0.9092974268256817 julia> broadcast(sin, [1,4,2]) 3-element Vector{Float64}: 0.8414709848078965 -0.7568024953079282 0.9092974268256817
R> jl(broadcast)(sin, c(1,4,2)) 3-element Vector{Float64}: 0.8414709848078965 -0.7568024953079282 0.9092974268256817
3. Splat avec
jl4Rjulia> Σ(x...) = sum(x) Σ (generic function with 1 method) julia> Σ(1,3,2,4.0), Σ(1,3,2) (10.0, 6) julia> julia> somme = splat(+) splat(+) julia> somme([1,4,2.0]) 7.0
R> ## Définition inside julia R> jl(`Σ(x...) = sum(x)`) Σ (generic function with 1 method) R> jl(Σ)(1,3,2,4.0) 10.0 R> jl(Σ)(1,3,2) 6.0 R> ## Directly in R R> Σ2 <- jl(splat)(jl(`+`)) R> Σ2(c(1,4,2)) 7.0 R> jl$somme <- jl(splat)(jl(`+`)) R> jl$somme # saved inside R splat(+) R> jl$somme(c(1,4,2)) 7.0
4. Opérateurs
isa et <: in jl4Rjulia> isa(1, Number) true julia> isa(1.0, Real) true julia> 1.0 isa Real true julia> typeof(1.0) Float64 julia> <:(typeof(1.0),Real) true julia> typeof(1.0) <: Real true
R> jl(isa)(1, Number) # 1 is converted to 1.0 true R> jl(`<:`)(jl(typeof)(1.0), Real) true R> "%isa%" <- function(a, b) jl(isa)(substitute(a),substitute(b)) R> "%<:%" <- function(a, b) jl(`<:`)(substitute(a),substitute(b)) R> 1 %isa% Number true R> jl(typeof)(1.0) Float64 R> Float64 %<:% Real true
►jl4R usage plus avancé en Statistique
- conversion
data.frame(R) etDataFrame(julia) - conversion
factor(R) etCategoricalArrays(julia)
1. Data Frame en
jl4Rjulia> using DataFrames julia> df = DataFrame(a=1:4, b=repeat([true,false],2)) 4×2 DataFrame Row │ a b │ Int64 Bool ─────┼────────────── 1 │ 1 true 2 │ 2 false 3 │ 3 true 4 │ 4 false julia> nrow(df), ncol(df) (4, 2) julia> DataFrame <: AbstractArray false
R> ## Done for you: jlusing(DataFrames) R> jl$df=jl(data.frame(a=1:4, b=rep(c(TRUE,FALSE), 2))) R> R(jl$df) a b 1 1 TRUE 2 2 FALSE 3 3 TRUE 4 4 FALSE R> jl(nrow)(jl$df) 4 R> jl(nrow)(df) # since df is not an R object 100 R> jl(`<:`)(DataFrame, AbstractArray) false R> DataFrame %<:% AbstractArray false R> df %isa% DataFrame true
2. Facteur en
jl4Rjulia> using CategoricalArrays julia> fa = categorical(["titi","toto","titi"]) 3-element CategoricalArray{String,1,UInt32}: "titi" "toto" "titi" julia> levels(fa) 2-element Vector{String}: "titi" "toto" julia> CategoricalArray <: AbstractArray true
R> ## Done for you: jlusing(CategoricalArrays) R> jl$fa=jl(factor(c("titi","toto","titi"))) R> jl(levels)(fa) # or jl(levels)(jl$fa) 2-element Vector{String}: "titi" "toto" R> jl(`<:`)(CategoricalArray, AbstractArray) true R> CategoricalArray %<:% AbstractArray true R> fa %isa% CategoricalArray true
3. Distributions en
jl4Rjulia> using Distributions julia> X = Normal() Distributions.Normal{Float64}(μ=0.0, σ=1.0) julia> v = rand(X,100); julia> first(v,3) 3-element Vector{Float64}: 0.672055737087241 0.9243752210498981 -0.6667872696116819 julia> fit(Normal,v) Distributions.Normal{Float64}(μ=-0.007001277416232235, σ=1.0695559018928316)
R> jlusing(Distributions) R> X = jl(`Normal()`) R> v = jl(rand)(X,100L) R> jl(first)(v,3L) 3-element Vector{Float64}: 0.6444173886277711 0.6857940824115728 -0.6174543803374274 R> jl(fit)(Normal,v) Normal{Float64}(μ=0.0696875098396355, σ=0.8761342680134331)
4. Idée pour développement de
VirtualAgeModelsJL via jl4Rjulia> using VirtualAgeModels julia> m_sim = @vam(Time & Type ~ (ARA∞(0.4) | Weibull(.001,2.5))); julia> df = rand(m_sim,100); julia> first(df,3) 3×2 DataFrame Row │ Time Type │ Float64 Int64 ─────┼───────────────── 1 │ 5.40654 -1 2 │ 17.5875 -1 3 │ 21.2511 -1 julia> m_mle = @vam(Time & Type ~ (ARA∞(0.4) | Weibull(.001,2.5))); julia> mle(m_mle, df); julia> params(m_mle) 3-element Vector{Float64}: 0.001 2.378926039345273 0.6207250737313206
R> jlusing(VirtualAgeModels) R> m_sim = jl(`@vam(Time & Type ~ (ARA∞(0.4) | Weibull(.001,2.5)))`); R> df = jl(rand)(m_sim,100L); R> jl(first)(df,3L) 3×2 DataFrame Row │ Time Type │ Float64 Int64 ─────┼───────────────── 1 │ 9.21979 -1 2 │ 22.8823 -1 3 │ 32.0563 -1 R> m_mle = jl(`@vam(Time & Type ~ (ARA∞(0.4) | Weibull(.001,2.5)))`); R> invisible(jl(mle)(m_mle, df)) R> jl(params)(m_mle) 3-element Vector{Float64}: 0.001 3.8842976814115007 0.22028854669374964
►Commentaires
Quelques mots sur développement de jl4R
-
classe
jlvaluecommeexternal pointer -
garbage collector de
juliaappelé dès que "garbage collector" nettoie un objetjlvalue -
fonction de classe
jlfunction -
gestion des erreurs
jlexception
Limites de jl4R
-
jl4Rest un package encore (très) expérimental et donc non considéré comme stable - installation dépend (au moins pour l'instant) d'un compilateur (voir site https://github.com/rcqls/jl4R