R vs Julia

Julia successeuse naturelle de R (😂 Adeline Joke)

R(ulia) Drouilhet

https://cqls.dyndoc.fr/RvsJulia (ou /Cours)

Auteur et Objectifs de la présentation
  • Amateur du R depuis très longtemps (en 1999 après avoir découvert S/Splus en 1994) et ensuite de Julia (autour de 2013)
  • Fan de Julia car fan de R: Julia est (pour moi) le successeur naturel de R!
  • Messages principaux de la présentation sur Julia:
    1. performance et simplicité: Julia langage compilé (comme C++) mais s'utilisant comme un langage interprété (comme R et python grâce au package Revise.jl)
    2. mode prototypage (proche de l'algorithmique): Julia langage expressif (comme R) facile à utiliser pour coder des premières idées
    3. "dispatching": programmation (de type objet) commune en Julia (multiple) et R (single et multiple)
  • Rulia package R pour:
    1. appel de Julia dans le R et surtout sans dépendance à Rcpp (comme JuliaCall)
    2. alternative naturelle à Rcpp pour "booster" vos packages R

Petit questionnaire de bienvenue sur les Types en R et Julia!


En R et Julia, on parle de Type d'objet. Ici on ne considère les Types d'objets s'enregistrant dans des variables.
Quelles sont les types de base en R?
Quelles sont les types de base en Julia?

Points communs / Différences R vs Julia

Points communs (principaux)
  • Programmation objet de type dispatching:
    • Julia multiple dispatching
    • R single dispatching (programmation S3) et multiple dispatching (programmation S4)
    • mode utilisateur en R et Julia similaires:
      1. appel de fonction générique (redirigée vers méthode spécifique)
      2. appel de fonction simple
  • Metaprogrammation: le code c'est de la donnée (langage de type LISP) traité par le langage lui-même
  • Packages: outils proposés via un unique système de packages
Différences majeures
  • Julia langage compilé vs R langage interprété
  • Julia "one language" vs R "glue language" (80% à 90% du code exécuté développé en C, C++, Fortran)
  • Julia abondance de types (à la fois statiques et dynamiques) vs R peu de types (principalement statiques)

Comparer codes R et Julia

  Crible d'Ératosthène: nombres premiers

Retranscriptions R et Julia du code proposé sur wikipedia
Crible d'Ératosthène en R
premier = function(n) {
    l = rep(TRUE,n)
    l[1] = FALSE
    for(i in 2:sqrt(n)) {
        if(l[i]) {
            for(j in seq(i*i,n,by=i)) {
                l[j] = FALSE
            }
        }
    }
    return((1:n)[l]) # ou which(l)
}
premier(120)
system.time(premier(10000000))
R> premier = function(n) {
+     l = rep(TRUE,n)
+     l[1] = FALSE
+     for(i in 2:sqrt(n)) {
+         if(l[i]) {
+             for(j in seq(i*i,n,by=i)) {
+                 l[j] = FALSE
+             }
+         }
+     }
+     return((1:n)[l]) # ou which(l)
+ }
R> premier(120)
 [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67
[20]  71  73  79  83  89  97 101 103 107 109 113
R> system.time(premier(10000000))
utilisateur     système      écoulé 
      4.098       0.116       4.216

Crible d'Ératosthène en Julia
function premier(n) # ou premier = function(n)
    l = fill(true,n)
    l[1] = false
    for i in 2:round(Int,sqrt(n))
        if l[i]
            for j in i*i:i:n 
                l[j] = false
            end
        end
    end
    return (1:n)[l] # ou findall(l)
end
premier(120)
@time begin;premier(10000000);nothing;end
julia> function premier(n) # ou premier = function(n)
           l = fill(true,n)
           l[1] = false
           for i in 2:round(Int,sqrt(n))
               if l[i]
                   for j in i*i:i:n 
                       l[j] = false
                   end
               end
           end
           return (1:n)[l] # ou findall(l)
       end
premier (generic function with 1 method)
julia> premier(120)
30-element Vector{Int64}:
   2
   3
   5
   7
  11
  13
  17
  19
  23
  29
  31
  37
  41
  43
  47
  53
  59
  61
  67
  71
  73
  79
  83
  89
  97
 101
 103
 107
 109
 113
julia> @time begin;premier(10000000);nothing;end
  0.126929 seconds (6 allocations: 14.607 MiB, 51.81% gc time)

Commentaires
  • Langage interprété vs langage compilé:
    • en R, le code de la fonction est exécuté ligne par ligne (chaque ligne exécutant du code C précompilé)
    • en Julia, la fonction est précompilée une seule fois (avant sa première utilisation) à la volée (J.I.T. Just In Time compilation comme en Rcpp inline) et ensuite exécutée d'un seul bloc. Les macros @code_... permettent l'introspection des codes générés
  • Performance: voir l'ordre de grandeur des temps d'exécution dans les 2 langages
  • Allocation mémoire: en R, 1:10 est un vecteur de 10 entiers (32bits) quand, en Julia, 1:10 (resp. i*i:i:n) est un itérateur de taille 2 (resp. 3) entiers (64bits)
Avec le package Rulia on peut directement faire du Julia dans le R et ainsi booster les performances (sans passer par Rcpp)
Crible d'Ératosthène en Rulia
require(Rulia)
jl(`
function premier(n)
    l = fill(true,n)
    l[1] = false
    for i in 2:round(Int,sqrt(n))
        if l[i]
            for j in i*i:i:n
                l[j] = false
            end
        end
    end
    return (1:n)[l]
end
`)
jl_premier <- jl(premier)
R(jl_premier(120L))
system.time(jl_premier(10000000L))
system.time(R(jl_premier(10000000L)))
R> require(Rulia)
R> jl(`
+ function premier(n)
+     l = fill(true,n)
+     l[1] = false
+     for i in 2:round(Int,sqrt(n))
+         if l[i]
+             for j in i*i:i:n
+                 l[j] = false
+             end
+         end
+     end
+     return (1:n)[l]
+ end
+ `)
premier (generic function with 1 method)
R> jl_premier <- jl(premier)
R> R(jl_premier(120L))
 [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67
[20]  71  73  79  83  89  97 101 103 107 109 113
R> system.time(jl_premier(10000000L))
utilisateur     système      écoulé 
      0.057       0.000       0.057 
R> system.time(R(jl_premier(10000000L)))
utilisateur     système      écoulé 
      0.079       0.012       0.090

Commentaire sur Rcpp
Sans trop de difficulté, un code équivalent relativement simple est proposé en Rcpp (mode inline et Rcpp Sugar)!
Crible d'Ératosthène en Rcpp
require(Rcpp)

cppFunction("
IntegerVector premier(int n) {
    LogicalVector l = rep(true,n + 1);
    l[0] = false;
    l[1] = false;
    for(int i = 2; i < sqrt(n); i++) {
        if(l[i]) {
            for(int j = i*i; j < n; j+=i) {
                l[j] = false;
            }
        }
    }
    IntegerVector res = seq(0,n);
    return res[l]; // seq(0,n)[l] ne marche pas!
}
")
premier(120)

system.time(premier(10000000))
R> require(Rcpp)

R> cppFunction("
+ IntegerVector premier(int n) {
+     LogicalVector l = rep(true,n + 1);
+     l[0] = false;
+     l[1] = false;
+     for(int i = 2; i < sqrt(n); i++) {
+         if(l[i]) {
+             for(int j = i*i; j < n; j+=i) {
+                 l[j] = false;
+             }
+         }
+     }
+     IntegerVector res = seq(0,n);
+     return res[l]; // seq(0,n)[l] ne marche pas!
+ }
+ ")
R> premier(120)
 [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67
[20]  71  73  79  83  89  97 101 103 107 109 113 120

R> system.time(premier(10000000))
utilisateur     système      écoulé 
      0.192       0.016       0.208

  Le "dispatching (single and multiple)" en action

   "Single dispatching" du R vs "multiple dispatching" du Julia

Notons que par défaut le R utilise le mode S3 qui est du "single dispatching" équivalent en termes de programmation objet à du python.
Single dispatching S3 en R
summary
methods(summary)
str(summary.default)
summary(1:10) # redirigé vers summary.default(1:10)
summary(matrix(1:10,nr=2)) # redirigé vers summary.matrix(matrix(1:10,nr=2))
R> summary
function (object, ...) 
UseMethod("summary")
<bytecode: 0x564dcaa76c28>
<environment: namespace:base>
R> methods(summary)
 [1] summary.aov                    summary.aovlist*              
 [3] summary.aspell*                summary.check_packages_in_dir*
 [5] summary.connection             summary.data.frame            
 [7] summary.Date                   summary.default               
 [9] summary.ecdf*                  summary.factor                
[11] summary.glm                    summary.infl*                 
[13] summary.jlexception*           summary.jlexceptions*         
[15] summary.lm                     summary.loess*                
[17] summary.manova                 summary.matrix                
[19] summary.mlm*                   summary.nls*                  
[21] summary.packageStatus*         summary.POSIXct               
[23] summary.POSIXlt                summary.ppr*                  
[25] summary.prcomp*                summary.princomp*             
[27] summary.proc_time              summary.srcfile               
[29] summary.srcref                 summary.stepfun               
[31] summary.stl*                   summary.table                 
[33] summary.tukeysmooth*           summary.warnings              
see '?methods' for accessing help and source code
R> str(summary.default)
function (object, ..., digits, quantile.type = 7)  
R> summary(1:10) # redirigé vers summary.default(1:10)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    3.25    5.50    5.50    7.75   10.00 
R> summary(matrix(1:10,nr=2)) # redirigé vers summary.matrix(matrix(1:10,nr=2))
       V1             V2             V3             V4             V5       
 Min.   :1.00   Min.   :3.00   Min.   :5.00   Min.   :7.00   Min.   : 9.00  
 1st Qu.:1.25   1st Qu.:3.25   1st Qu.:5.25   1st Qu.:7.25   1st Qu.: 9.25  
 Median :1.50   Median :3.50   Median :5.50   Median :7.50   Median : 9.50  
 Mean   :1.50   Mean   :3.50   Mean   :5.50   Mean   :7.50   Mean   : 9.50  
 3rd Qu.:1.75   3rd Qu.:3.75   3rd Qu.:5.75   3rd Qu.:7.75   3rd Qu.: 9.75  
 Max.   :2.00   Max.   :4.00   Max.   :6.00   Max.   :8.00   Max.   :10.00

  • Le "multiple dispatching" existe en R avec le S4 (et un peu avec le nouveau S7) mais très limité (signature à nombre fixé d'arguments)
  • Le "multiple dispatching" est le fondement du fonctionnement de Julia
Multiple dispatching: rand en Julia
rand()
rand(2)
rand(1:10)
rand(1:10,2)
rand(1:10,2,2)
rand(1:10,2,2,2)
rand(1:10, (2,2))
rand(1:10, (2,2,2))
rand(Int,2)
rand(Int8,2)
rand(Bool,2)
[(typemin(T),typemax(T)) for T=(Int8,Int,Bool)]
using Distributions
rand(Normal())
rand(Normal(),2)
rand(Normal(),2,2)
## exploring
rand
import Random
length(methods(rand, Random))
length(methods(rand, Distributions))
julia> rand()
0.5882789414757903
julia> rand(2)
2-element Vector{Float64}:
 0.6535751471259258
 0.24497707942489866
julia> rand(1:10)
4
julia> rand(1:10,2)
2-element Vector{Int64}:
 9
 2
julia> rand(1:10,2,2)
2×2 Matrix{Int64}:
 5  6
 5  3
julia> rand(1:10,2,2,2)
2×2×2 Array{Int64, 3}:
[:, :, 1] =
 1   6
 9  10

[:, :, 2] =
 5  1
 4  2
julia> rand(1:10, (2,2))
2×2 Matrix{Int64}:
 2  6
 4  5
julia> rand(1:10, (2,2,2))
2×2×2 Array{Int64, 3}:
[:, :, 1] =
 1  5
 7  8

[:, :, 2] =
 3  2
 8  2
julia> rand(Int,2)
2-element Vector{Int64}:
 -7239707682465649319
  -429825186516224282
julia> rand(Int8,2)
2-element Vector{Int8}:
 -71
  12
julia> rand(Bool,2)
2-element Vector{Bool}:
 0
 1
julia> [(typemin(T),typemax(T)) for T=(Int8,Int,Bool)]
3-element Vector{Tuple{Integer, Integer}}:
 (-128, 127)
 (-9223372036854775808, 9223372036854775807)
 (false, true)
julia> using Distributions
julia> rand(Normal())
-0.40157031420476236
julia> rand(Normal(),2)
2-element Vector{Float64}:
  1.54136115228539
 -0.6603749257896785
julia> rand(Normal(),2,2)
2×2 Matrix{Float64}:
 -1.57258   -0.730296
  0.266283   0.991521
julia> ## exploring
julia> rand
rand (generic function with 181 methods)
julia> import Random
julia> length(methods(rand, Random))
76
julia> length(methods(rand, Distributions))
104

Tryptique Julia
Le "multiple dispatching" en Julia n'est possible que parce qu'il est combiné à:
  • arbre des Types (tout objet a un Type en Julia)
  • Just In Time compilation (compilation à la volée)

   "Double dispatching" pour l'opérateur de multiplication

Echec single dispatching pour la multiplication en R
2 * 1:10 # ou `*`(2, 1:10)
1:10 * 1:10 # ou `*`(1:10, 1:10)
1:2 * t(1:2)
t(t(1:2)) * t(1:2)
1:2 %*% t(1:2) # pas de multiple dispatching!
t(t(1:2)) %*% t(1:2)
R> 2 * 1:10 # ou `*`(2, 1:10)
 [1]  2  4  6  8 10 12 14 16 18 20
R> 1:10 * 1:10 # ou `*`(1:10, 1:10)
 [1]   1   4   9  16  25  36  49  64  81 100
R> 1:2 * t(1:2)
     [,1] [,2]
[1,]    1    4
R> t(t(1:2)) * t(1:2)
Erreur dans t(t(1:2)) * t(1:2) : tableaux de tailles inadéquates

R> 1:2 %*% t(1:2) # pas de multiple dispatching!
     [,1] [,2]
[1,]    1    2
[2,]    2    4
R> t(t(1:2)) %*% t(1:2)
     [,1] [,2]
[1,]    1    2
[2,]    2    4

Multiple dispatching: multiplication en Julia
v = collect(1:10)
2v
2(1:10) # ou *(2,1:10)
2 * 1:10 # ou (:)(2 * 1, 10)
[1,2] * [1,2]' # ou [1,2] * transpose([1,2])
[1;2] * [1 2]  # vcat(1,2) * hcat(1,2)
A = [1 2; 3 4; 5 6] # ou vcat(hcat(1,2), hcat(3,4), hcat(5,6))
hvcat((2,2,2), 1,2,3,4,5,6)
hvcat((2,2,2), 1:6...)
2A # ou 2*A ou 2 * A
A * A'
A' * A
first(methods(*),5)
length(methods(*))
julia> v = collect(1:10)
10-element Vector{Int64}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
julia> 2v
10-element Vector{Int64}:
  2
  4
  6
  8
 10
 12
 14
 16
 18
 20
julia> 2(1:10) # ou *(2,1:10)
2:2:20
julia> 2 * 1:10 # ou (:)(2 * 1, 10)
2:10
julia> [1,2] * [1,2]' # ou [1,2] * transpose([1,2])
2×2 Matrix{Int64}:
 1  2
 2  4
julia> [1;2] * [1 2]  # vcat(1,2) * hcat(1,2)
2×2 Matrix{Int64}:
 1  2
 2  4
julia> A = [1 2; 3 4; 5 6] # ou vcat(hcat(1,2), hcat(3,4), hcat(5,6))
3×2 Matrix{Int64}:
 1  2
 3  4
 5  6
julia> hvcat((2,2,2), 1,2,3,4,5,6)
3×2 Matrix{Int64}:
 1  2
 3  4
 5  6
julia> hvcat((2,2,2), 1:6...)
3×2 Matrix{Int64}:
 1  2
 3  4
 5  6
julia> 2A # ou 2*A ou 2 * A
3×2 Matrix{Int64}:
  2   4
  6   8
 10  12
julia> A * A'
3×3 Matrix{Int64}:
  5  11  17
 11  25  39
 17  39  61
julia> A' * A
2×2 Matrix{Int64}:
 35  44
 44  56
julia> first(methods(*),5)
[1] *(::Missing, ::Missing) @ Base missing.jl:122
[2] *(::Missing) @ Base missing.jl:101
[3] *(d::Missing, x::Union{AbstractChar, AbstractString}) @ Base missing.jl:174
[4] *(::Missing, ::Number) @ Base missing.jl:123
[5] *(x::Bool, z::Complex{Bool}) @ Base complex.jl:312
julia> length(methods(*))
374

   Programmation mode dispatching

Single dispatching: constructeur pour Point
Point = function(x = 0, y = 0) {
    obj <- list(x = x, y = y)
    class(obj) <- "Point"
    obj
}

print # fonction générique
print.Point = function(obj, ...) {
    cat("Point(",obj$x, ",", obj$y,")
",sep="")
}

Point()

# Affichage par défaut si print.Point pas défini. "default" peut être vue comme la classe "Any" de Julia 
print.default(Point())
R> Point = function(x = 0, y = 0) {
+     obj <- list(x = x, y = y)
+     class(obj) <- "Point"
+     obj
+ }

R> print # fonction générique
function (x, ...) 
UseMethod("print")
<bytecode: 0x564dc7729348>
<environment: namespace:base>
R> print.Point = function(obj, ...) {
+     cat("Point(",obj$x, ",", obj$y,")
",sep="")
+ }

R> Point()
Point(0,0)

R> # Affichage par défaut si print.Point pas défini. "default" peut être vue comme la classe "Any" de Julia 
R> print.default(Point())
$x
[1] 0

$y
[1] 0

attr(,"class")
[1] "Point"

Multiple dispatching: constructeur implicite pour Point
mutable struct Point
    x::Float64
    y::Float64
end

Point(1.0, 2.0) # Constructeur implicite

## Nouveau constructeur avec arguments nommés avec valeurs par défaut
Point(; x = 0.0, y = 0.0) = Point(x, y)

Point()
Point(y=2.0)
methods(Point)
Point(y=2)
Point(1//2, 3//4)
Point(1//2, 3)
julia> mutable struct Point
           x::Float64
           y::Float64
       end
julia> 
julia> Point(1.0, 2.0) # Constructeur implicite
Point(1.0, 2.0)
julia> 
julia> ## Nouveau constructeur avec arguments nommés avec valeurs par défaut
julia> Point(; x = 0.0, y = 0.0) = Point(x, y)
Point
julia> 
julia> Point()
Point(0.0, 0.0)
julia> Point(y=2.0)
Point(0.0, 2.0)
julia> methods(Point)
# 3 methods for type constructor:
 [1] Point(x::Float64, y::Float64)
     @ none:2
 [2] Point(; x, y)
     @ none:1
 [3] Point(x, y)
     @ none:2
julia> Point(y=2)
Point(0.0, 2.0)
julia> Point(1//2, 3//4)
Point(0.5, 0.75)
julia> Point(1//2, 3)
Point(0.5, 3.0)

Single dispatching: méthodes pour Point
# Nouvelle fonction générique
translate = function(obj, ...) UseMethod("translate")

# Méthode par défaut (comme la classe "Any" de Julia)
translate.default = function(obj, ...) cat("rien à faire
")

# Méthode spécifique à la classe Point
translate.Point = function(obj, x, y) {
    if(missing(y)) {
        if(inherits(x, "Point")) 
            Point(obj$x + x$x, obj$y + x$y)
        else 
            Point(obj$x + x[1], obj$y + x[2])
    } else
        Point(obj$x + x, obj$y + y)
}

p = Point(1,2)
translate(p, p)
translate(p, 2, 1)
translate(p, c(3,2))
R> # Nouvelle fonction générique
R> translate = function(obj, ...) UseMethod("translate")

R> # Méthode par défaut (comme la classe "Any" de Julia)
R> translate.default = function(obj, ...) cat("rien à faire
")

R> # Méthode spécifique à la classe Point
R> translate.Point = function(obj, x, y) {
+     if(missing(y)) {
+         if(inherits(x, "Point")) 
+             Point(obj$x + x$x, obj$y + x$y)
+         else 
+             Point(obj$x + x[1], obj$y + x[2])
+     } else
+         Point(obj$x + x, obj$y + y)
+ }

R> p = Point(1,2)
R> translate(p, p)
Point(2,4)
R> translate(p, 2, 1)
Point(3,3)
R> translate(p, c(3,2))
Point(4,4)

Multiple dispatching: méthodes pour Point
## Nouvelles méthodes! La fonction générique "translate" est créée dès la création de la première méthode (Pas de UseMethod comme en R!)
translate(p::Point, x::Float64, y::Float64) = Point(p.x + x, p.y + y)
translate(p::Point, p2::Point) = translate(p, p2.x, p2.y)
translate(p::Point, p2::Tuple{Float64, Float64}) = translate(p, p2[1], p2[2])
translate(p::Point, p2::Vector{Float64}) = translate(p, p2[1], p2[2])
translate

p = Point(1.0, 2.0)
translate(p, p)
translate(p, 2.0, 1.0)
translate(p, (3.0, 2.0))
translate(p, [3.0, 2.0])

translate!(p::Point, x::Float64, y::Float64) = p.x, p.y = p.x + x, p.y + y
translate!(p::Point, p2::Point) = translate!(p, p2.x, p2.y)
translate!(p::Point, p2::Tuple{Float64, Float64}) = translate!(p, p2[1], p2[2])
translate!(p::Point, p2::Vector{Float64}) = translate!(p, p2[1], p2[2])

translate!(p, (0.5, 0.5))
p
julia> ## Nouvelles méthodes! La fonction générique "translate" est créée dès la création de la première méthode (Pas de UseMethod comme en R!)
julia> translate(p::Point, x::Float64, y::Float64) = Point(p.x + x, p.y + y)
translate (generic function with 4 methods)
julia> translate(p::Point, p2::Point) = translate(p, p2.x, p2.y)
translate (generic function with 4 methods)
julia> translate(p::Point, p2::Tuple{Float64, Float64}) = translate(p, p2[1], p2[2])
translate (generic function with 4 methods)
julia> translate(p::Point, p2::Vector{Float64}) = translate(p, p2[1], p2[2])
translate (generic function with 4 methods)
julia> translate
translate (generic function with 4 methods)
julia> 
julia> p = Point(1.0, 2.0)
Point(1.0, 2.0)
julia> translate(p, p)
Point(2.0, 4.0)
julia> translate(p, 2.0, 1.0)
Point(3.0, 3.0)
julia> translate(p, (3.0, 2.0))
Point(4.0, 4.0)
julia> translate(p, [3.0, 2.0])
Point(4.0, 4.0)
julia> 
julia> translate!(p::Point, x::Float64, y::Float64) = p.x, p.y = p.x + x, p.y + y
translate! (generic function with 1 method)
julia> translate!(p::Point, p2::Point) = translate!(p, p2.x, p2.y)
translate! (generic function with 2 methods)
julia> translate!(p::Point, p2::Tuple{Float64, Float64}) = translate!(p, p2[1], p2[2])
translate! (generic function with 3 methods)
julia> translate!(p::Point, p2::Vector{Float64}) = translate!(p, p2[1], p2[2])
translate! (generic function with 4 methods)
julia> 
julia> translate!(p, (0.5, 0.5))
(1.5, 2.5)
julia> p
Point(1.5, 2.5)

Multiple dispatching: + et - pour Point
UnionPoint = Union{Point, Tuple{Float64, Float64}, Vector{Float64}}
import Base.+
+(p::Point, p2::UnionPoint) = translate(p, p2)
p + p
p + [3.0, 2.0]
p2 = p + (3.0, 2.0)
import Base.-
-(p::Point) = Point(-p.x, -p.y)
-(p::Point, p2::Point) = p + -p2
-p
p2 - p
julia> UnionPoint = Union{Point, Tuple{Float64, Float64}, Vector{Float64}}
Union{Tuple{Float64, Float64}, Point, Vector{Float64}}
julia> import Base.+
julia> +(p::Point, p2::UnionPoint) = translate(p, p2)
+ (generic function with 266 methods)
julia> p + p
Point(3.0, 5.0)
julia> p + [3.0, 2.0]
Point(4.5, 4.5)
julia> p2 = p + (3.0, 2.0)
Point(4.5, 4.5)
julia> import Base.-
julia> -(p::Point) = Point(-p.x, -p.y)
- (generic function with 239 methods)
julia> -(p::Point, p2::Point) = p + -p2
- (generic function with 239 methods)
julia> -p
Point(-1.5, -2.5)
julia> p2 - p
Point(3.0, 2.0)

  Calcul vectoriel (broadcasting en Julia) et Boucle for

Exemple de Loi conditionnelle: $n$ réalisations de $Y \leadsto \mathcal{N}(X, 1.5)$ sachant que $X\leadsto \mathcal{N}(3,2)$
En R, c'est direct par vectorisation.
Loi conditionnelle en R
n = 10
x = rnorm(n,3,2)
(yx = rnorm(n,x,1.5))
R> n = 10
R> x = rnorm(n,3,2)
R> (yx = rnorm(n,x,1.5))
 [1]  4.6911403  3.6606581  1.0514205  3.9437504  4.1594289  3.8937451
 [7] -0.1050789  2.5849682  5.6847011  0.6243298

La forme la plus ressemblante en Julia utilise la notion de "broadcasting" (diffusion/vectorisation par une boucle for sur l'expression)
Loi conditionnelle en Julia
using Distributions
n = 10
x = rand(Normal(3,2), n);
y = rand.(Normal.(x,1.5)) # ou @. rand(Normal(x,1.5))
julia> using Distributions
julia> n = 10
10
julia> x = rand(Normal(3,2), n);
julia> y = rand.(Normal.(x,1.5)) # ou @. rand(Normal(x,1.5))
10-element Vector{Float64}:
  4.3691573653609685
  0.5061521810820693
  5.023327765029702
 -1.0644152780783798
  1.8618761731117683
  2.2987525186700073
  3.5866333911247557
  6.166501095037341
  1.5483673051275924
  9.473727504698621

A la différence du R, on peut définir l'objet "la loi de probabilité" et ici celle du vecteur aléatoire $\mathbf{X}$
Loi conditionnelle en Julia
X = fill(Normal(3,2), n)
typeof(X)
y = rand.(Normal.(rand.(X),1.5)) # ou @. rand(Normal(rand(X),1.5))
# ou en 2 étapes
Y = Normal.(rand.(X),1.5)
y = rand.(Y)
julia> X = fill(Normal(3,2), n)
10-element Vector{Distributions.Normal{Float64}}:
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
 Distributions.Normal{Float64}(μ=3.0, σ=2.0)
julia> typeof(X)
Vector{Normal{Float64}} (alias for Array{Distributions.Normal{Float64}, 1})
julia> y = rand.(Normal.(rand.(X),1.5)) # ou @. rand(Normal(rand(X),1.5))
10-element Vector{Float64}:
  1.3941085198770744
  2.7192930223714
  0.966705431832747
  5.074318911443579
 -1.4413798997037692
  4.895348542891374
  2.358984038323269
 -1.7661197129708746
  3.098856831740648
  5.069847427647938
julia> # ou en 2 étapes
julia> Y = Normal.(rand.(X),1.5)
10-element Vector{Distributions.Normal{Float64}}:
 Distributions.Normal{Float64}(μ=-0.19821719430184226, σ=1.5)
 Distributions.Normal{Float64}(μ=11.249372225280048, σ=1.5)
 Distributions.Normal{Float64}(μ=2.9489364565045837, σ=1.5)
 Distributions.Normal{Float64}(μ=4.674577816261513, σ=1.5)
 Distributions.Normal{Float64}(μ=2.9143918934477893, σ=1.5)
 Distributions.Normal{Float64}(μ=5.512650813670501, σ=1.5)
 Distributions.Normal{Float64}(μ=5.218877744970909, σ=1.5)
 Distributions.Normal{Float64}(μ=0.14546007977953312, σ=1.5)
 Distributions.Normal{Float64}(μ=3.4661222013560575, σ=1.5)
 Distributions.Normal{Float64}(μ=4.978873166711072, σ=1.5)
julia> y = rand.(Y)
10-element Vector{Float64}:
 -3.048105785355899
 13.119980373544985
  2.734517638313852
  2.0835106794557112
  0.7252625628526845
  6.5063677295833005
  5.59264708455326
  0.41890196421942744
  4.295117291153683
  5.6891671290643835

Loi conditionnelle en Julia
x = [rand(Normal(3,2)) for _ = 1:n]
y = [rand(Normal(μ,1.5)) for μ = x]
julia> x = [rand(Normal(3,2)) for _ = 1:n]
10-element Vector{Float64}:
 5.486825414938051
 2.261761823098594
 1.4846492888104867
 7.044394613077319
 1.281850478676397
 3.2630895154489066
 2.7145054624247154
 2.2046027879392103
 5.906943017128002
 4.344413599423314
julia> y = [rand(Normal(μ,1.5)) for μ = x]
10-element Vector{Float64}:
 7.602292431916963
 0.9660483510330454
 3.2605509617171045
 6.0254805723967495
 1.9329095166559684
 1.5834633916386265
 2.5170618150515245
 2.2950961996405903
 5.268945466334383
 4.016849673107867

Loi conditionnelle en Julia
X = (rand(Normal(3,2)) for _ = 1:n)
typeof(X)
Y = (rand(Normal(μ,1.5)) for μ = X)
typeof(Y)

## collecter Y sans collecter X
collect(Y)
julia> X = (rand(Normal(3,2)) for _ = 1:n)
Base.Generator{UnitRange{Int64}, var"#256#257"}(var"#256#257"(), 1:10)
julia> typeof(X)
Base.Generator{UnitRange{Int64}, var"#256#257"}
julia> Y = (rand(Normal(μ,1.5)) for μ = X)
Base.Generator{Base.Generator{UnitRange{Int64}, var"#256#257"}, var"#258#259"}(var"#258#259"(), Base.Generator{UnitRange{Int64}, var"#256#257"}(var"#256#257"(), 1:10))
julia> typeof(Y)
Base.Generator{Base.Generator{UnitRange{Int64}, var"#256#257"}, var"#258#259"}
julia> 
julia> ## collecter Y sans collecter X
julia> collect(Y)
10-element Vector{Float64}:
  0.36598255041965133
  0.056476763014507825
 -1.4058160282091028
  7.581087542164001
  3.729931473460135
  1.494252705721596
 -0.19482716134946135
  4.120843632457409
  6.262488775048499
 -3.212067290859674

Loi conditionnelle en Julia
x = rand(Normal(3,2), n);
y = map(μ -> rand(Normal(μ,1.5)), x)
y = map(x) do μ
    rand(Normal(μ,1.5))
end
julia> x = rand(Normal(3,2), n);
julia> y = map(μ -> rand(Normal(μ,1.5)), x)
10-element Vector{Float64}:
  2.113481454861714
  1.8611618369875949
  5.092415834624551
  3.984896847207592
  5.110478382725282
  2.62996639917499
  5.128376399454387
  6.162332288291868
 -2.100658906623645
 -1.0296920774882596
julia> y = map(x) do μ
           rand(Normal(μ,1.5))
       end
10-element Vector{Float64}:
  1.560500334409705
  1.3511762341284266
  2.2918705087279587
 -1.1834732812720539
  5.775746096256306
  3.944169659915564
  7.1298611490346016
  7.1893107744323235
 -0.8169989537898281
 -0.41147178792342576

Loi conditionnelle en Julia
x = rand(Normal(3,2), n);
y = similar(x)
for i in eachindex(x)
    y[i] = rand(Normal(x[i],1.5))
end
y
julia> x = rand(Normal(3,2), n);
julia> y = similar(x)
10-element Vector{Float64}:
 6.9299236202998e-310
 6.9299236202998e-310
 6.9299236202998e-310
 6.9299236202998e-310
 6.92992362054923e-310
 6.92992362074844e-310
 6.9299236213018e-310
 6.92992362134606e-310
 6.92992362077057e-310
 6.9299236207927e-310
julia> for i in eachindex(x)
           y[i] = rand(Normal(x[i],1.5))
       end
julia> y
10-element Vector{Float64}:
  1.7274647978171687
 -1.428240441148426
  1.6846484616905288
  5.735829961165866
  4.689577124104667
  3.4756394074216574
  0.523175867161167
  6.573749377021547
 10.147360359449232
  6.111318615806665

R et Julia en résumé

R en résumé
version simplifiée
  • langage interprété avec paradigme multi-langages car de type glue (à vocation à lancer du code précompilé en C/Fortran)
  • types de base: fonctions (function) et listes ordonnées d'objets (de nature homogène vector et hétérogène list)
  • concept d'attributs: metadonnées attachés aux objets (ayant un type de base) permettant:
    • attribut class: programmation objet single dispatching S3
    • attribut names: listes nommées
    • attribut dim: matrices (matrix), tableaux (array)
    • attributs levels, class="factor": variables qualitatives (factor)
    • attributs names, row.names, class="data.frame": matrice de données (data.frame)
    • ...
version complétée
  • types de base supplémentaires:
    • environment: structure dynamique utile pour gestion du workspace et namespace de package
    • expression (call/language, name/symbol): dédiés pour la métaprogrammation
  • plusieurs principes de programmation objet (en plus de S3):
    • "multiple dispatching" S4
    • dynamique (à la python) R6 (utilisant les objets de type environment)
    • "single" et "multiple dispatching" S7
  • expressivité grâce à la métaprogrammation
  • basé sur un unique système de packages
  • API C étendue via interaction avec C++ grâce au package Rcpp
Julia en résumé
version simplifiée
  • langage compilé avec paradigme one language rétablissant la boucle for
  • basé sur l'incontournable tryptique:
    1. "multiple dispatching" (unique principe de programmation objet) géré de manière interne (avec définition implicite/automatique des fonctions génériques à la définition de la première fonction/méthode)
    2. arbres des types très riches (à la fois dynamiques et statiques): instantiables (feuilles) et abstraits (les autres noeuds) avec type abstrait racine Any (par défaut quand non précisé pour prototypage de code)
    3. Just In Time compilation ayant l'avantage de le faire ressembler (à l'utilisation) à un langage interprété
version complétée
  • expressivité grâce à la métaprogrammation et notamment le système de macro (nom d'appel commençant par @).
  • objectif (avoué) de parallélisation des calculs en plein essor
  • notion d'interfaces implicites permettant:
    • création de types itérables (iterate()) et donc utilisables dans une boucle for
    • création de types indexables (getindex(), setindex!()) sous le format: obj[...] et obj[...] = v
    • création de types (hcat(), vcat()) manipulables sous un format [ a b ; c d; e f]
  • basé sur un unique système de packages (proposant de base des installations dans des environnements locaux non invasifs)
  • intéraction avec:
    • ccall téléguidant les librairies dynamiques compilées (.dll, .dylib, .so) créées dans langages C/C++ et autres.
    • Interaction avec R via RCall.jl (interface de ), avec Python via PythonCall.jl et PyCall.jl, ...
  • rmq: futures versions de Julia proposeront la création d'exécutable binaire et de librairies dynamiques

Rulia une tentative de remplacer Rcpp

Rulia en résumé
  • Rulia le package R pour appeler Julia dans le R
  • attention: Rulia en phase de développement (ne pas l'utiliser en phase de production).
  • communication entre R et Julia via leur API C
  • Rulia pour créer des packages R qui téléguident les packages Julia
  • alternative à JuliaCall mais sans dépendence à Rcpp
Fonctionnalités Rulia
  1. fonction jl(...)
    • jl(`[CodeJulia]`): exécute dans Julia du code [CodeJulia] et retourne une jlvalue dans R
    • jl([VarJulia] = [ObjetR]): convertit dans Julia de l'objet R [ObjetR] sous le nom [VarJulia]
    • jl([FctJulia])([arg1], [arg2], ...): évalue dans Julia de la fonction [FctJulia] avec les arguments jl([arg1]), jl([arg2]), ... et retourne une jlvalue dans R
  2. appel de fichier/package Julia
    • jlusing([PackageJulia]): chargement du package Julia [PackageJulia]
    • jlinclude("[FichierJulia].jl"): évalue code du fichier [FichierJulia].jl
    • jlinclude([PackageR]::[FichierJulia]"): évalue code du fichier [FichierJulia].jl dans le répertoire du package [PackageR]/inst/[FichierJulia].jl
  3. fonction R(...)
    • R([ValueJulia]): convertit en R la jlvalue [ValueJulia]
    • R([ObjetR]): crée une jlvalue utilisant espace mémoire R de [ObjetR] (quand c'est possible)

Interactions entre R et Julia

  Julia dans R avec Rulia

Rulia
require(Rulia)
jlinclude(Rulia::RCall)
zz <- runif(3)
zz
Rzz <- R(zz) # objet jlvalue téléguidant l'objet zz de R
Rzz
jl(typeof)(Rzz) # reconnu en Julia comme un véritable vecteur
Rzz[1] <- 2
Rzz
zz # Incroyable mais vrai!
zz[1] <- 1 # en R, un nouveau vecteur est créé!
zz # les vecteurs en R sont statiques à la différence de Julia
Rzz # la preuve: le vecteur Julia n'est pas modifié!
R> require(Rulia)
R> jlinclude(Rulia::RCall)
R> zz <- runif(3)
R> zz
[1] 0.94396580 0.42768618 0.03775241
R> Rzz <- R(zz) # objet jlvalue téléguidant l'objet zz de R
R> Rzz
3-element Vector{Float64}:
 0.9439658001065254
 0.4276861846446991
 0.03775241179391742
R> jl(typeof)(Rzz) # reconnu en Julia comme un véritable vecteur
Vector{Float64} (alias for Array{Float64, 1})
R> Rzz[1] <- 2
R> Rzz
3-element Vector{Float64}:
 2.0
 0.4276861846446991
 0.03775241179391742
R> zz # Incroyable mais vrai!
[1] 2.00000000 0.42768618 0.03775241
R> zz[1] <- 1 # en R, un nouveau vecteur est créé!
R> zz # les vecteurs en R sont statiques à la différence de Julia
[1] 1.00000000 0.42768618 0.03775241
R> Rzz # la preuve: le vecteur Julia n'est pas modifié!
3-element Vector{Float64}:
 2.0
 0.4276861846446991
 0.03775241179391742

  R dans Julia avec RCall.jl

RCall.jl
using RCall
R"""
df <- data.frame(a=runif(3),b=TRUE)
df$b[2] <- FALSE
df
"""
@rget df
df.a[2]=1
df
@rput df
R"df"
R"""
require(Rulia)
jlinclude(Rulia::RCall)
jl(d=R(df))
jl()$d
"""
Main.d
df
# Un peu de Magie! Modification de d en Julia
Main.d.a[3] = 5
# Et Abracadabra! Modification de la data.frame alors que R est un langage statique
R"df"
julia> using RCall
julia> R"""
       df <- data.frame(a=runif(3),b=TRUE)
       df$b[2] <- FALSE
       df
       """
RCall.RObject{RCall.VecSxp}
          a     b
1 0.3977069  TRUE
2 0.1661558 FALSE
3 0.1662044  TRUE

julia> @rget df
3×2 DataFrame
 Row │ a         b
     │ Float64   Bool
─────┼─────────────────
   1 │ 0.397707   true
   2 │ 0.166156  false
   3 │ 0.166204   true
julia> df.a[2]=1
1
julia> df
3×2 DataFrame
 Row │ a         b
     │ Float64   Bool
─────┼─────────────────
   1 │ 0.397707   true
   2 │ 1.0       false
   3 │ 0.166204   true
julia> @rput df
3×2 DataFrame
 Row │ a         b
     │ Float64   Bool
─────┼─────────────────
   1 │ 0.397707   true
   2 │ 1.0       false
   3 │ 0.166204   true
julia> R"df"
RCall.RObject{RCall.VecSxp}
          a     b
1 0.3977069  TRUE
2 1.0000000 FALSE
3 0.1662044  TRUE

julia> R"""
       require(Rulia)
       jlinclude(Rulia::RCall)
       jl(d=R(df))
       jl()$d
       """
RCall.RObject{RCall.ExtPtrSxp}
3×2 DataFrame
 Row │ a         b
     │ Float64   Int32
─────┼─────────────────
   1 │ 0.397707      1
   2 │ 1.0           0
   3 │ 0.166204      1

julia> Main.d
3×2 DataFrame
 Row │ a         b
     │ Float64   Int32
─────┼─────────────────
   1 │ 0.397707      1
   2 │ 1.0           0
   3 │ 0.166204      1
julia> df
3×2 DataFrame
 Row │ a         b
     │ Float64   Bool
─────┼─────────────────
   1 │ 0.397707   true
   2 │ 1.0       false
   3 │ 0.166204   true
julia> # Un peu de Magie! Modification de d en Julia
julia> Main.d.a[3] = 5
5
julia> # Et Abracadabra! Modification de la data.frame alors que R est un langage statique
julia> R"df"
RCall.RObject{RCall.VecSxp}
          a     b
1 0.3977069  TRUE
2 1.0000000 FALSE
3 5.0000000  TRUE

WebR Console




    

Sélection Jeu de données

WebR Plot