Formation Julia

Introduction

Julia est un langage proposé pour la première fois en 2012 par ses créateurs Jeff Bezanson, Stefan Karpinski, Viral B. Shah sous la tutelle de Alan Edelman (julialang.org, wikipedia). Pour une version de Julia plus orientée monde professionnel, voir aussi julia compulting et juliaPro.

Présentation générale de julia

  • Ses points forts:
    • langage à vocation calcul scientifique et numérique
    • nouveau langage interprété avec pour objectif la performance de l'ordre du langage compilé C:
      • JIT (production transparente du code C précompilé avant execution: un peu comme le Rcpp inline du R mais sans l'effort d'écrire le code C++)
      • interfaçage natif des librairies C (pas besoin de Rcpp)
      • cohabitation entre structures immutables et mutables
      • intégration des outils de calcul parallèle (comme un objectif initial)
    • système de packages uniformisé (comme en R)
    • paradigme mode utilisateur/mode développeur (comme le R, matlab, ...)
      • vocation à proposer des outils avancés de développement pour construire des outils faciles à utiliser pour les utilisateurs
      • mode utilisateur: typage dynamique (comme en R), utilisation assez proche de R avec un REPL et installation facile des outils via le système de packages
      • mode développeur: développement de package basé sur une programmation orienté objet (création d'objet) de type "multiple dispatching" (à la S4 du R mais bien plus efficace et légère)
        • types (et non classes) composites définissables par utilisateur/développeur s'intégrant dans l'arbre hierarchique des types standard prédéfinis (entier, réel, chaîne de caractère, tableau)
        • type plutôt que classe car les méthodes ne sont pas attachées au classe
        • les méthodes ne sont que des fonctions (comme en R, S3 et S4) pouvant avoir le même nom (correspondant à la tâche à exécuter) pour des signatures (liste des arguments) différentes (c'est le "multiple dispatching")
        • tout est objet en julia (même les fonctions comme en R) avec un type associé choisi dans l'arbre hiérarchique de tous les types
        • un système d'interface permettant de manipuler de manière plus expressive (via opérateurs) des outils définis par des fonctions (méthodes)
    • très bonne interaction avec d'autres langages
      • C de manière native
      • R via le package RCall.jl qui permet notamment dans le REPL de julia de basculer vers un REPL R (via touche $)
      • shell accessible via le REPL (touche ;)
      • C++, python via des packages
  • compaison avec R
    • principales différences avec R
      • rétablissement de la boucle for performante en julia et ainsi pas de programmation vectorielle requise pour performance ou d'utilisation d'outil du type Rcpp
      • types atomiques Int8, Int16, ..., Float64 (structures immutables non indiçables)
      • structures immutables Tuple, String
      • système de macro basé sur son système d'expression à la lisp
      • pas d'objet environnement et pas de persistance de session (pourquoi pas à venir en version partielle)
      • pas de notion d'attributs (ou pas vu mais est-elle utile en julia compte tenu de son mode OOP)
    • les principaux points communs
      • système basé sur des appels de fonctions (qui sont des objets comme les autres)
      • les structures vectorielles, matricielles avec opérations vectorielles associées
      • mode lisp-like avec expression
      • mode statistique
      • Gadfly.jl un package inspiré de ggplot2
      • weave.jl un package pour faire des rapports automatiques reproducibles (comme SWeave et son successeur knitr)

Installation

  • Julia est un langage multiplatforme reposant sur LLVM. Comme ses "potentiels" concurrents, le site officiel https://julialang.org propose les installateurs sur les différentes plateformes.
  • Julia-VSCode est une extension de l'éditeur de texte VSCode qui est une application de type Web (basée sur electron). Initialement, il existait une solution Juno, s'appuyant sur l'éditeur de texte Atom l'inspirateur de VSCode, avait été proposée. Fort heureusement, les 2 projets ont convergé vers un même et unique projet (comme c'est souvent le cas dans la communauté Julia).
  • Jupyter est une sorte de REPL (Read-Eval-Print-Loop) amélioré, appelé Notebook. Voir installation https://jupyter.org/install.html
  • Pluto.jl est un autre Notebook proposé comme un package julia. Sa caractéristique qui le distingue de jupyter est qu'il est "reactive" (s'appuyant sur l'outil javascript observable). C'est en quelque sorte comme une feuille de calcul d'un tableur utilisant des formules.

Démarrage rapide

Quelques mots avant nos premiers pas en Julia

Sans rentrer dans les détails, soulignons que :
  • Julia a une documentation officielle (à mon goût) excellente. Elle a longtemps (avant le lancement de la version 1.0.0) été le document à lire pour découvrir le langage Julia. Au lancement de la version 1.0.0, le site web a mis en avant l'écosystème de tous les domaines d'application de Julia.
  • Julia repose sur un système de paquetage unique (package en anglais) qui cherche à reprendre le meilleur des systèmes existants sur les autres langages dont notamment la notion d'environnements. L'outil permettant la gestion des packages est lui même distribué comme un package: Pkg.jl Pour commencer, on va installer nos premiers packages. Il y a 3 manières de faire :
    1. Dans la console en mode gestion de paquets : taper ] pour passer en mode gestion de package puis une fois le prompt changé en pkg> saisir add StatsKit Plots GR. La grande touche Effacer caractère précédent permet de revenir au prompt julia>.
    2. Dans la console ou un script Julia : using Pkg puis pkg"add StatsKit Plots GR"
    3. Dans la console ou un script Julia : using Pkg puis Pkg.add("StatsKit");Pkg.add("Plots");Pkg.add("GR")
    Pour de plus amples explications sur la gestion des paquets aller à la documentation officielle de Pkg.jl.
  • Julia est modulaire avec son organisation de paquets rassemblés sur le site https://julialang.org/packages/. Les paquets sont généralement disponibles sur Github et, facilement et astucieusement identifiables par leur nom incluant l'extension .jl. Il devient facile de "googler" un package Julia si l'on connaît son nom (même approximativement) en rajoutant l'extension .jl.
  • Tout d'abord rappelons l'éventuel désagrément que l'on peut ressentir à la première utilisation de Julia. Rappelons-nous qu'à la différence de langage comme python et R, Julia n'est pas un langage interprété pur mais qu'il repose sur un système de compilation à la volée (compilation JIT pour Just In Time). Ne pas confondre avec la compilation JIT du R qui lui transforme à la volée le code dans un format byte-compilé qui n'est pas équivalent à un code généré, par exemple, par un compilateur C.

Une mini-session basique

Une session très simple
v=[1,3,2]
sum(v)
# ajout d'éléments
push!(v, 4)
push!(v, 3)
v2=[5;4;2] # équivalent à [5,4,2]
append!(v,v2)
v
# extraction
v[1]
v[end]
v[2:4]
v[2:end]
v[2:end-2]
julia> v=[1,3,2]
3-element Vector{Int64}:
 1
 3
 2
julia> sum(v)
6
julia> # ajout d'éléments
julia> push!(v, 4)
4-element Vector{Int64}:
 1
 3
 2
 4
julia> push!(v, 3)
5-element Vector{Int64}:
 1
 3
 2
 4
 3
julia> v2=[5;4;2] # équivalent à [5,4,2]
3-element Vector{Int64}:
 5
 4
 2
julia> append!(v,v2)
8-element Vector{Int64}:
 1
 3
 2
 4
 3
 5
 4
 2
julia> v
8-element Vector{Int64}:
 1
 3
 2
 4
 3
 5
 4
 2
julia> # extraction
julia> v[1]
1
julia> v[end]
2
julia> v[2:4]
3-element Vector{Int64}:
 3
 2
 4
julia> v[2:end]
7-element Vector{Int64}:
 3
 2
 4
 3
 5
 4
 2
julia> v[2:end-2]
5-element Vector{Int64}:
 3
 2
 4
 3
 5

Une mini-session plus avancée sur la vectorisation

Plus avancés mais intéressants
# opération vectorielle
v^2
3^2
v.^2
sin(v)
sin.(v)
# vecteur défini par extension
[sin(x) for x in v]
# composition de fonction
uppercase ∘ first
noms = ["eric","pierre","valérie"]
map(uppercase ∘ first, noms)
map(uppercasefirst,noms)
# Les String sont immu(t)ables
noms2 = similar(noms)
noms2 .= uppercasefirst.(noms)
@. noms2 = uppercasefirst(noms)
# en moins joli mais toutefois très riche
noms3=similar(noms);
for (i,v) = enumerate(noms) # = ou in
	noms3[i]=uppercasefirst(noms[i])
end
noms3
julia> # opération vectorielle
julia> v^2
ERROR: MethodError: no method matching ^(::Vector{Int64}, ::Int64)
Closest candidates are:
  ^(!Matched::Union{AbstractChar, AbstractString}, ::Integer) at strings/basic.jl:730
  ^(!Matched::LinearAlgebra.Hermitian, ::Integer) at ~/tools/install/julia-1.8.2/share/julia/stdlib/v1.8/LinearAlgebra/src/symmetric.jl:696
  ^(!Matched::LinearAlgebra.Hermitian{T, S} where S<:(AbstractMatrix{<:T}), ::Real) where T at ~/tools/install/julia-1.8.2/share/julia/stdlib/v1.8/LinearAlgebra/src/symmetric.jl:707
  ...
julia> 3^2
9
julia> v.^2
8-element Vector{Int64}:
  1
  9
  4
 16
  9
 25
 16
  4
julia> sin(v)
ERROR: MethodError: no method matching sin(::Vector{Int64})
Closest candidates are:
  sin(!Matched::T) where T<:Union{Float32, Float64} at special/trig.jl:29
  sin(!Matched::LinearAlgebra.Hermitian{var"#s884", S} where {var"#s884"<:Complex, S<:(AbstractMatrix{<:var"#s884"})}) at ~/tools/install/julia-1.8.2/share/julia/stdlib/v1.8/LinearAlgebra/src/symmetric.jl:731
  sin(!Matched::Union{LinearAlgebra.Hermitian{var"#s885", S}, LinearAlgebra.Symmetric{var"#s885", S}} where {var"#s885"<:Real, S}) at ~/tools/install/julia-1.8.2/share/julia/stdlib/v1.8/LinearAlgebra/src/symmetric.jl:727
  ...
julia> sin.(v)
8-element Vector{Float64}:
  0.8414709848078965
  0.1411200080598672
  0.9092974268256817
 -0.7568024953079282
  0.1411200080598672
 -0.9589242746631385
 -0.7568024953079282
  0.9092974268256817
julia> # vecteur défini par extension
julia> [sin(x) for x in v]
8-element Vector{Float64}:
  0.8414709848078965
  0.1411200080598672
  0.9092974268256817
 -0.7568024953079282
  0.1411200080598672
 -0.9589242746631385
 -0.7568024953079282
  0.9092974268256817
julia> # composition de fonction
julia> uppercase ∘ first
uppercase ∘ first
julia> noms = ["eric","pierre","valérie"]
3-element Vector{String}:
 "eric"
 "pierre"
 "valérie"
julia> map(uppercase ∘ first, noms)
3-element Vector{Char}:
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'P': ASCII/Unicode U+0050 (category Lu: Letter, uppercase)
 'V': ASCII/Unicode U+0056 (category Lu: Letter, uppercase)
julia> map(uppercasefirst,noms)
3-element Vector{String}:
 "Eric"
 "Pierre"
 "Valérie"
julia> # Les String sont immu(t)ables
julia> noms2 = similar(noms)
3-element Vector{String}:
 #undef
 #undef
 #undef
julia> noms2 .= uppercasefirst.(noms)
3-element Vector{String}:
 "Eric"
 "Pierre"
 "Valérie"
julia> @. noms2 = uppercasefirst(noms)
3-element Vector{String}:
 "Eric"
 "Pierre"
 "Valérie"
julia> # en moins joli mais toutefois très riche
julia> noms3=similar(noms);
julia> for (i,v) = enumerate(noms) # = ou in
         noms3[i]=uppercasefirst(noms[i])
       end
julia> noms3
3-element Vector{String}:
 "Eric"
 "Pierre"
 "Valérie"

Une mini-session classification

Un exemple de clustering avec kmeans (Cluster.jl via StatsKit.jl)
using RDatasets, StatsKit, Plots
iris = RDatasets.dataset("datasets", "iris"); # load the data

features = collect(Matrix(iris[:, 1:4])'); # features to use for clustering
result = kmeans(features, 3); # run K-means for the 3 clusters
julia> using RDatasets, StatsKit, Plots
julia> iris = RDatasets.dataset("datasets", "iris"); # load the data
150×5 DataFrame
 Row │ SepalLength  SepalWidth  PetalLength  PetalWidth  Species
     │ Float64      Float64     Float64      Float64     Cat…
─────┼──────────────────────────────────────────────────────────────
   15.1         3.5          1.4         0.2  setosa
   24.9         3.0          1.4         0.2  setosa
   34.7         3.2          1.3         0.2  setosa
   44.6         3.1          1.5         0.2  setosa
   55.0         3.6          1.4         0.2  setosa
   65.4         3.9          1.7         0.4  setosa
   74.6         3.4          1.4         0.3  setosa
   85.0         3.4          1.5         0.2  setosa
   94.4         2.9          1.4         0.2  setosa
  104.9         3.1          1.5         0.1  setosa
  115.4         3.7          1.5         0.2  setosa
  124.8         3.4          1.6         0.2  setosa
  134.8         3.0          1.4         0.1  setosa
  144.3         3.0          1.1         0.1  setosa
  155.8         4.0          1.2         0.2  setosa
  165.7         4.4          1.5         0.4  setosa
  175.4         3.9          1.3         0.4  setosa
  185.1         3.5          1.4         0.3  setosa
  195.7         3.8          1.7         0.3  setosa
  205.1         3.8          1.5         0.3  setosa
  215.4         3.4          1.7         0.2  setosa
  225.1         3.7          1.5         0.4  setosa
  234.6         3.6          1.0         0.2  setosa
  245.1         3.3          1.7         0.5  setosa
  254.8         3.4          1.9         0.2  setosa
  265.0         3.0          1.6         0.2  setosa
  275.0         3.4          1.6         0.4  setosa
  285.2         3.5          1.5         0.2  setosa
  295.2         3.4          1.4         0.2  setosa
  304.7         3.2          1.6         0.2  setosa
  314.8         3.1          1.6         0.2  setosa
  325.4         3.4          1.5         0.4  setosa
  335.2         4.1          1.5         0.1  setosa
  345.5         4.2          1.4         0.2  setosa
  354.9         3.1          1.5         0.2  setosa
  365.0         3.2          1.2         0.2  setosa
  375.5         3.5          1.3         0.2  setosa
  384.9         3.6          1.4         0.1  setosa
  394.4         3.0          1.3         0.2  setosa
  405.1         3.4          1.5         0.2  setosa
  415.0         3.5          1.3         0.3  setosa
  424.5         2.3          1.3         0.3  setosa
  434.4         3.2          1.3         0.2  setosa
  445.0         3.5          1.6         0.6  setosa
  455.1         3.8          1.9         0.4  setosa
  464.8         3.0          1.4         0.3  setosa
  475.1         3.8          1.6         0.2  setosa
  484.6         3.2          1.4         0.2  setosa
  495.3         3.7          1.5         0.2  setosa
  505.0         3.3          1.4         0.2  setosa
  517.0         3.2          4.7         1.4  versicolor
  526.4         3.2          4.5         1.5  versicolor
  536.9         3.1          4.9         1.5  versicolor
  545.5         2.3          4.0         1.3  versicolor
  556.5         2.8          4.6         1.5  versicolor
  565.7         2.8          4.5         1.3  versicolor
  576.3         3.3          4.7         1.6  versicolor
  584.9         2.4          3.3         1.0  versicolor
  596.6         2.9          4.6         1.3  versicolor
  605.2         2.7          3.9         1.4  versicolor
  615.0         2.0          3.5         1.0  versicolor
  625.9         3.0          4.2         1.5  versicolor
  636.0         2.2          4.0         1.0  versicolor
  646.1         2.9          4.7         1.4  versicolor
  655.6         2.9          3.6         1.3  versicolor
  666.7         3.1          4.4         1.4  versicolor
  675.6         3.0          4.5         1.5  versicolor
  685.8         2.7          4.1         1.0  versicolor
  696.2         2.2          4.5         1.5  versicolor
  705.6         2.5          3.9         1.1  versicolor
  715.9         3.2          4.8         1.8  versicolor
  726.1         2.8          4.0         1.3  versicolor
  736.3         2.5          4.9         1.5  versicolor
  746.1         2.8          4.7         1.2  versicolor
  756.4         2.9          4.3         1.3  versicolor
  766.6         3.0          4.4         1.4  versicolor
  776.8         2.8          4.8         1.4  versicolor
  786.7         3.0          5.0         1.7  versicolor
  796.0         2.9          4.5         1.5  versicolor
  805.7         2.6          3.5         1.0  versicolor
  815.5         2.4          3.8         1.1  versicolor
  825.5         2.4          3.7         1.0  versicolor
  835.8         2.7          3.9         1.2  versicolor
  846.0         2.7          5.1         1.6  versicolor
  855.4         3.0          4.5         1.5  versicolor
  866.0         3.4          4.5         1.6  versicolor
  876.7         3.1          4.7         1.5  versicolor
  886.3         2.3          4.4         1.3  versicolor
  895.6         3.0          4.1         1.3  versicolor
  905.5         2.5          4.0         1.3  versicolor
  915.5         2.6          4.4         1.2  versicolor
  926.1         3.0          4.6         1.4  versicolor
  935.8         2.6          4.0         1.2  versicolor
  945.0         2.3          3.3         1.0  versicolor
  955.6         2.7          4.2         1.3  versicolor
  965.7         3.0          4.2         1.2  versicolor
  975.7         2.9          4.2         1.3  versicolor
  986.2         2.9          4.3         1.3  versicolor
  995.1         2.5          3.0         1.1  versicolor
 1005.7         2.8          4.1         1.3  versicolor
 1016.3         3.3          6.0         2.5  virginica
 1025.8         2.7          5.1         1.9  virginica
 1037.1         3.0          5.9         2.1  virginica
 1046.3         2.9          5.6         1.8  virginica
 1056.5         3.0          5.8         2.2  virginica
 1067.6         3.0          6.6         2.1  virginica
 1074.9         2.5          4.5         1.7  virginica
 1087.3         2.9          6.3         1.8  virginica
 1096.7         2.5          5.8         1.8  virginica
 1107.2         3.6          6.1         2.5  virginica
 1116.5         3.2          5.1         2.0  virginica
 1126.4         2.7          5.3         1.9  virginica
 1136.8         3.0          5.5         2.1  virginica
 1145.7         2.5          5.0         2.0  virginica
 1155.8         2.8          5.1         2.4  virginica
 1166.4         3.2          5.3         2.3  virginica
 1176.5         3.0          5.5         1.8  virginica
 1187.7         3.8          6.7         2.2  virginica
 1197.7         2.6          6.9         2.3  virginica
 1206.0         2.2          5.0         1.5  virginica
 1216.9         3.2          5.7         2.3  virginica
 1225.6         2.8          4.9         2.0  virginica
 1237.7         2.8          6.7         2.0  virginica
 1246.3         2.7          4.9         1.8  virginica
 1256.7         3.3          5.7         2.1  virginica
 1267.2         3.2          6.0         1.8  virginica
 1276.2         2.8          4.8         1.8  virginica
 1286.1         3.0          4.9         1.8  virginica
 1296.4         2.8          5.6         2.1  virginica
 1307.2         3.0          5.8         1.6  virginica
 1317.4         2.8          6.1         1.9  virginica
 1327.9         3.8          6.4         2.0  virginica
 1336.4         2.8          5.6         2.2  virginica
 1346.3         2.8          5.1         1.5  virginica
 1356.1         2.6          5.6         1.4  virginica
 1367.7         3.0          6.1         2.3  virginica
 1376.3         3.4          5.6         2.4  virginica
 1386.4         3.1          5.5         1.8  virginica
 1396.0         3.0          4.8         1.8  virginica
 1406.9         3.1          5.4         2.1  virginica
 1416.7         3.1          5.6         2.4  virginica
 1426.9         3.1          5.1         2.3  virginica
 1435.8         2.7          5.1         1.9  virginica
 1446.8         3.2          5.9         2.3  virginica
 1456.7         3.3          5.7         2.5  virginica
 1466.7         3.0          5.2         2.3  virginica
 1476.3         2.5          5.0         1.9  virginica
 1486.5         3.0          5.2         2.0  virginica
 1496.2         3.4          5.4         2.3  virginica
 1505.9         3.0          5.1         1.8  virginica
julia> 
julia> features = collect(Matrix(iris[:, 1:4])'); # features to use for clustering
4×150 Matrix{Float64}:
 5.1  4.9  4.7  4.6  5.0  5.4  4.6  5.0  4.4  4.9  5.4  4.8  4.8  4.3  5.8  5.7  5.4  5.1  5.7  5.1  5.4  5.1  4.6  5.1  4.8  5.0  5.0  5.2  5.2  4.7  4.8  5.4  5.2  5.5  4.9  5.0  5.5  4.9  4.4  5.1  5.0  4.5  4.4  5.0  5.1  4.8  5.1  4.6  5.3  5.0  7.0  6.4  6.9  5.5  6.5  5.7  6.3  4.9  6.6  5.2  5.0  5.9  6.0  6.1  5.6  6.7  5.6  5.8  6.2  5.6  5.9  6.1  6.3  6.1  6.4  6.6  6.8  6.7  6.0  5.7  5.5  5.5  5.8  6.0  5.4  6.0  6.7  6.3  5.6  5.5  5.5  6.1  5.8  5.0  5.6  5.7  5.7  6.2  5.1  5.7  6.3  5.8  7.1  6.3  6.5  7.6  4.9  7.3  6.7  7.2  6.5  6.4  6.8  5.7  5.8  6.4  6.5  7.7  7.7  6.0  6.9  5.6  7.7  6.3  6.7  7.2  6.2  6.1  6.4  7.2  7.4  7.9  6.4  6.3  6.1  7.7  6.3  6.4  6.0  6.9  6.7  6.9  5.8  6.8  6.7  6.7  6.3  6.5  6.2  5.9
 3.5  3.0  3.2  3.1  3.6  3.9  3.4  3.4  2.9  3.1  3.7  3.4  3.0  3.0  4.0  4.4  3.9  3.5  3.8  3.8  3.4  3.7  3.6  3.3  3.4  3.0  3.4  3.5  3.4  3.2  3.1  3.4  4.1  4.2  3.1  3.2  3.5  3.6  3.0  3.4  3.5  2.3  3.2  3.5  3.8  3.0  3.8  3.2  3.7  3.3  3.2  3.2  3.1  2.3  2.8  2.8  3.3  2.4  2.9  2.7  2.0  3.0  2.2  2.9  2.9  3.1  3.0  2.7  2.2  2.5  3.2  2.8  2.5  2.8  2.9  3.0  2.8  3.0  2.9  2.6  2.4  2.4  2.7  2.7  3.0  3.4  3.1  2.3  3.0  2.5  2.6  3.0  2.6  2.3  2.7  3.0  2.9  2.9  2.5  2.8  3.3  2.7  3.0  2.9  3.0  3.0  2.5  2.9  2.5  3.6  3.2  2.7  3.0  2.5  2.8  3.2  3.0  3.8  2.6  2.2  3.2  2.8  2.8  2.7  3.3  3.2  2.8  3.0  2.8  3.0  2.8  3.8  2.8  2.8  2.6  3.0  3.4  3.1  3.0  3.1  3.1  3.1  2.7  3.2  3.3  3.0  2.5  3.0  3.4  3.0
 1.4  1.4  1.3  1.5  1.4  1.7  1.4  1.5  1.4  1.5  1.5  1.6  1.4  1.1  1.2  1.5  1.3  1.4  1.7  1.5  1.7  1.5  1.0  1.7  1.9  1.6  1.6  1.5  1.4  1.6  1.6  1.5  1.5  1.4  1.5  1.2  1.3  1.4  1.3  1.5  1.3  1.3  1.3  1.6  1.9  1.4  1.6  1.4  1.5  1.4  4.7  4.5  4.9  4.0  4.6  4.5  4.7  3.3  4.6  3.9  3.5  4.2  4.0  4.7  3.6  4.4  4.5  4.1  4.5  3.9  4.8  4.0  4.9  4.7  4.3  4.4  4.8  5.0  4.5  3.5  3.8  3.7  3.9  5.1  4.5  4.5  4.7  4.4  4.1  4.0  4.4  4.6  4.0  3.3  4.2  4.2  4.2  4.3  3.0  4.1  6.0  5.1  5.9  5.6  5.8  6.6  4.5  6.3  5.8  6.1  5.1  5.3  5.5  5.0  5.1  5.3  5.5  6.7  6.9  5.0  5.7  4.9  6.7  4.9  5.7  6.0  4.8  4.9  5.6  5.8  6.1  6.4  5.6  5.1  5.6  6.1  5.6  5.5  4.8  5.4  5.6  5.1  5.1  5.9  5.7  5.2  5.0  5.2  5.4  5.1
 0.2  0.2  0.2  0.2  0.2  0.4  0.3  0.2  0.2  0.1  0.2  0.2  0.1  0.1  0.2  0.4  0.4  0.3  0.3  0.3  0.2  0.4  0.2  0.5  0.2  0.2  0.4  0.2  0.2  0.2  0.2  0.4  0.1  0.2  0.2  0.2  0.2  0.1  0.2  0.2  0.3  0.3  0.2  0.6  0.4  0.3  0.2  0.2  0.2  0.2  1.4  1.5  1.5  1.3  1.5  1.3  1.6  1.0  1.3  1.4  1.0  1.5  1.0  1.4  1.3  1.4  1.5  1.0  1.5  1.1  1.8  1.3  1.5  1.2  1.3  1.4  1.4  1.7  1.5  1.0  1.1  1.0  1.2  1.6  1.5  1.6  1.5  1.3  1.3  1.3  1.2  1.4  1.2  1.0  1.3  1.2  1.3  1.3  1.1  1.3  2.5  1.9  2.1  1.8  2.2  2.1  1.7  1.8  1.8  2.5  2.0  1.9  2.1  2.0  2.4  2.3  1.8  2.2  2.3  1.5  2.3  2.0  2.0  1.8  2.1  1.8  1.8  1.8  2.1  1.6  1.9  2.0  2.2  1.5  1.4  2.3  2.4  1.8  1.8  2.1  2.4  2.3  1.9  2.3  2.5  2.3  1.9  2.0  2.3  1.8
julia> result = kmeans(features, 3); # run K-means for the 3 clusters
Clustering.KmeansResult{Matrix{Float64}, Float64, Int64}([5.88360655737705 6.853846153846153 5.005999999999999; 2.740983606557377 3.0769230769230766 3.428000000000001; 4.388524590163935 5.715384615384615 1.4620000000000002; 1.4344262295081966 2.053846153846153 0.2459999999999999], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 1], [0.019980000000018094, 0.20038000000000977, 0.17398000000001446, 0.2759800000000041, 0.03557999999999595, 0.45838000000000534, 0.17238000000000397, 0.004380000000011819, 0.6519799999999947, 0.1415800000000189, 0.23278000000000532, 0.06438000000001409, 0.2507800000000202, 0.8339799999999968, 1.0283800000000127, 1.451580000000007, 0.42798000000000513, 0.02078000000001623, 0.6795800000000156, 0.15158000000000982, 0.21478000000000463, 0.10798000000001196, 0.40997999999999024, 0.14638000000002194, 0.23718000000000927, 0.20438000000001466, 0.043580000000005725, 0.0463799999999992, 0.04438000000001807, 0.16678000000001703, 0.17118000000000677, 0.1811800000000119, 0.5119800000000083, 0.8459800000000115, 0.12238000000002103, 0.12278000000000588, 0.2775800000000146, 0.06598000000001036, 0.578780000000009, 0.01318000000000552, 0.034380000000012956, 1.5575800000000157, 0.4475800000000021, 0.14958000000001448, 0.36277999999998656, 0.23238000000002046, 0.16837999999999909, 0.2227800000000002, 0.16398000000000934, 0.0223800000000125, 1.4950295857987896, 0.49408492340771204, 0.9742603550295428, 0.510642300456837, 0.4324455791453943, 0.06769148078471687, 0.6103144316044222, 2.4573636119322657, 0.6012980381617865, 0.7088390217683411, 2.3080193496371777, 0.10719967750603132, 0.6458882020962022, 0.17031443160436766, 0.7455603332437448, 0.7967078742273372, 0.1642488578339112, 0.28064230045686145, 0.4094947594732332, 0.48900295619458234, 0.5139209889814254, 0.21933082504700963, 0.49736361193225775, 0.202281644719136, 0.31785541521097116, 0.581625907014228, 1.0137570545551853, 0.6665680473373072, 0.055560333243732885, 1.0317898414404567, 0.7216259070142286, 0.9262160709486693, 0.30228164471917296, 0.5488390217683445, 0.3176914807847169, 0.487691480784747, 0.89670787422736, 0.3860521365224372, 0.24883902176831896, 0.37424885783390494, 0.2221177102929346, 0.15982262832568495, 0.23277344799785737, 2.3488390217683417, 0.13572426766997125, 0.1912980381617757, 0.11260951357159854, 0.15129803816179788, 2.7119537758667036, 0.13851115291585359, 0.6365680473372493, 0.7316259070142337, 0.1027218934910934, 0.41579881656801376, 0.159644970414206, 1.3473372781064938, 1.1085111529158809, 0.6365680473372777, 0.428106508875743, 0.7404142011833983, 0.5219526627218727, 0.5442603550295928, 0.05733727810647338, 0.7855603332437511, 1.4490029561945619, 0.4542603550295894, 0.24195266272187155, 2.229644970414199, 2.4073372781065245, 0.6844127922601331, 0.07810650887574866, 0.6653963988175065, 1.7650295857988567, 0.5703144316044018, 0.07579881656801035, 0.2804142011834472, 0.4065439398011108, 0.5091668906207758, 0.2981065088757191, 0.3388757396449762, 0.5465680473372743, 2.088875739644976, 0.3173372781064927, 0.6873636119322555, 1.2365680473372436, 0.9304142011834529, 0.5442603550295644, 0.3173372781064927, 0.38359312012894975, 0.10426035502956665, 0.1573372781064677, 0.4419526627218886, 0.7316259070142337, 0.11272189349114115, 0.2727218934911093, 0.3557988165680399, 0.8221177102929005, 0.3996449704141867, 0.6919526627218886, 0.7071996775060256], [61, 39, 50], [61, 39, 50], 78.8556658259767, 8, true)

Le graphique associé
# plot with the point color mapped to the assigned cluster index
scatter(iris.PetalLength, iris.PetalWidth, marker_z=result.assignments,
        color=:lightrainbow, legend=false)
julia> # plot with the point color mapped to the assigned cluster index
       scatter(iris.PetalLength, iris.PetalWidth, marker_z=result.assignments,
               color=:lightrainbow, legend=false)
image

Les bases du langage

Introduction aux types

Pour comprendre l'objectif premier de julia de proposer en un unique langage un outil de développement de codes performants et rapides, il est conseillé de comprendre que julia, à la différence de ses concurrents principaux python et R, est un langage qui compile à la volée (JIT Just In Time) et qu'il repose aussi sur un système d'héritage de types très riche utile pour spécifier les types des valeurs manipulées et enregistrées dans des variables. Toutefois, pour bénéficier de la même faciliter de prototypage que ses concurrents python et R, on peut dans un premier temps proposer un code sans spécifier le type des variables (à la différence d'un langage compilé) ce qui correspond en fait à les fixer au type Any qui est le type le plus général. Toutes les valeurs manipulées en julia sont de types appartenant à une hiérarchie et l'une des spécificités est que :
  • les types correspondant aux feuilles, appelés types concrets, sont les seuls à être instantiables pour créer de nouvels valeurs ou objets
  • les autres types de l'arbre sont des types abstraits non instantiables mais utiles pour définir des les types des variables utilisées dans un bout de code ou des paramètres d'une fonction
En guise d'introduction, manipulons les fonctionnalités permettant d'être informé sur les types des valeurs.
Types atomiques
typeof(1),typeof(1.0),typeof(true),typeof("1"),typeof('1')
isa(1,Int64),isa(1.0,Float64),isa(true,Bool)
isa("1",String),isa('1',Char)
supertype(Int64),isa(1,Signed),supertype(Signed),isa(1,Integer)
subtypes(Signed)
subtypes(Integer)
Int == Int64 # Int32 for 32bits computer
isconcretetype(Int),isconcretetype(Float64)
isabstracttype(Signed),isabstracttype(Integer)
julia> typeof(1),typeof(1.0),typeof(true),typeof("1"),typeof('1')
(Int64, Float64, Bool, String, Char)
julia> isa(1,Int64),isa(1.0,Float64),isa(true,Bool)
(true, true, true)
julia> isa("1",String),isa('1',Char)
(true, true)
julia> supertype(Int64),isa(1,Signed),supertype(Signed),isa(1,Integer)
(Signed, true, Integer, true)
julia> subtypes(Signed)
7-element Vector{Any}:
 BigInt
 BitIntegers.AbstractBitSigned
 Int128
 Int16
 Int32
 Int64
 Int8
julia> subtypes(Integer)
4-element Vector{Any}:
 Bool
 SentinelArrays.ChainedVectorIndex
 Signed
 Unsigned
julia> Int == Int64 # Int32 for 32bits computer
true
julia> isconcretetype(Int),isconcretetype(Float64)
(true, true)
julia> isabstracttype(Signed),isabstracttype(Integer)
(true, true)

Afin d'explorer les héritages de types, créons une fonction treetypes() (commentaires sur le code différés à la partie traitant du type function) proposant l'arbre des sous-types avec possibilité de limiter la profondeur de l'arbre.
cours_types_treetypes_function
function treetypes(typ::Type,depth::Int64=100000;level::Int64=0)
  subtyps=subtypes(typ)
  println("	"^level,isconcretetype(typ) ? "(C)" : "(A)"," ",typ)
  if level <= depth
    for subtyp in subtyps
      if !(subtyp in [Any,Function])
        treetypes(subtyp,depth,level=level+1)
      end
    end
  end
end
julia> function treetypes(typ::Type,depth::Int64=100000;level::Int64=0)
         subtyps=subtypes(typ)
         println("	"^level,isconcretetype(typ) ? "(C)" : "(A)"," ",typ)
         if level <= depth
           for subtyp in subtyps
             if !(subtyp in [Any,Function])
               treetypes(subtyp,depth,level=level+1)
             end
           end
         end
       end
treetypes (generic function with 2 methods)

Testons la fonction en l'appliquant au type Integer.
Héritage Integer
treetypes(Integer)
julia> treetypes(Integer)
(A) Integer
	(C) Bool
	(A) SentinelArrays.ChainedVectorIndex
	(A) Signed
		(C) BigInt
		(A) BitIntegers.AbstractBitSigned
			(C) BitIntegers.Int1024
			(C) BitIntegers.Int256
			(C) BitIntegers.Int512
		(C) Int128
		(C) Int16
		(C) Int32
		(C) Int64
		(C) Int8
	(A) Unsigned
		(A) BitIntegers.AbstractBitUnsigned
			(C) BitIntegers.UInt1024
			(C) BitIntegers.UInt256
			(C) BitIntegers.UInt512
		(C) UInt128
		(C) UInt16
		(C) UInt32
		(C) UInt64
		(C) UInt8

Les structures atomiques

  Les nombres Number

Pour les curieux désireux d'avoir une vue globale, proposons tout de suite l'arbre des sous-types de Number.
Héritage Number
treetypes(Number)
julia> treetypes(Number)
(A) Number
	(A) Complex
	(A) DualNumbers.Dual
	(C) Plots.Measurement
	(A) Real
		(A) AbstractFloat
			(C) BigFloat
			(C) Float16
			(C) Float32
			(C) Float64
		(A) AbstractIrrational
			(A) Irrational
		(A) FixedPointNumbers.FixedPoint
			(A) FixedPointNumbers.Fixed
			(A) FixedPointNumbers.Normed
		(A) ForwardDiff.Dual
		(A) Integer
			(C) Bool
			(A) SentinelArrays.ChainedVectorIndex
			(A) Signed
				(C) BigInt
				(A) BitIntegers.AbstractBitSigned
					(C) BitIntegers.Int1024
					(C) BitIntegers.Int256
					(C) BitIntegers.Int512
				(C) Int128
				(C) Int16
				(C) Int32
				(C) Int64
				(C) Int8
			(A) Unsigned
				(A) BitIntegers.AbstractBitUnsigned
					(C) BitIntegers.UInt1024
					(C) BitIntegers.UInt256
					(C) BitIntegers.UInt512
				(C) UInt128
				(C) UInt16
				(C) UInt32
				(C) UInt64
				(C) UInt8
		(A) Rational
		(A) Ratios.SimpleRatio
		(C) StatsBase.PValue
		(C) StatsBase.TestStat

Sans rentrer dans les détails, proposons quelques exemples de valeurs numériques étant des sous-types de Real.
Numérique - sous-types de Real
i = 1
2i
2*i
i += 1
ii = 0x01
typeof(ii)
isa(ii, Int)
isa(ii,Number)
x=1.5
typeof(x)
isa(x,Number)
isa(x,Float64)
2x
typeof(2x)
julia> i = 1
1
julia> 2i
2
julia> 2*i
2
julia> i += 1
2
julia> ii = 0x01
0x01
julia> typeof(ii)
UInt8
julia> isa(ii, Int)
false
julia> isa(ii,Number)
true
julia> x=1.5
1.5
julia> typeof(x)
Float64
julia> isa(x,Number)
true
julia> isa(x,Float64)
true
julia> 2x
3.0
julia> typeof(2x)
Float64

  les booléens

true et false sont les seules valeurs considérées comme des booléens.
Booléens
true
false
1 > 3
(1 > 3) & (4 > 2)
(1 > 3) | (4 > 2)
1 > 3 & 4 > 2
1 > 3 | 4 > 2
1 > 3 && 4 > 2
1 > 3 || 4 > 2
julia> true
true
julia> false
false
julia> 1 > 3
false
julia> (1 > 3) & (4 > 2)
false
julia> (1 > 3) | (4 > 2)
true
julia> 1 > 3 & 4 > 2
false
julia> 1 > 3 | 4 > 2
false
julia> 1 > 3 && 4 > 2
false
julia> 1 > 3 || 4 > 2
true

On peut éviter le & pour des comparaisons chaînées.

Comparaisons chainées
1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5
julia> 1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5
true

Une autoconversion des éléments lorsqu'ils ne sont pas du même type est opérée (grâce au mécanisme de promotion proposé par julia) lors de comparaison.

Autoconversion pour comparaison
1.0 > 3
julia> 1.0 > 3
false

Comme on le verra avec l'arbre des types et le mécanisme de promotion, julia applique l'autoconversion au booléen car considéré comme un numérique (Real)

Booléen comme un Real
isa(true,Real)
true > 3
true 
true + false + true
true + 0.5 + 2
julia> isa(true,Real)
true
julia> true > 3
false
julia> true 
true
julia> true + false + true
2
julia> true + 0.5 + 2
3.5

En revanche, à la différence de beaucoup d'autres langages, aucune "autoconversion" n'est appliquée lorsqu'il est attendu une condition logique comme dans des instructions if et while.

Pas f'autoconversion dans condition if
if true; 2; end
if 1; 2; end
julia> if true; 2; end
2
julia> if 1; 2; end
ERROR: TypeError: non-boolean (Int64) used in boolean context

  Les caractères (Char) et chaînes de caractères (String)

En julia et à la différence de nombreux autres langages (dont le R), les simples quotes représentent un caractère et non une chaîne de caractères.
Caractère un type spécial
'a'
''
julia> 'a'
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> ''
'': Unicode U+2200 (category Sm: Symbol, math)

Les double quotes servent à définir une chaîne de caractères.
Chaines de caractères
b = "tot"
c = "tit"
julia> b = "tot"
"tot"
julia> c = "tit"
"tit"

Les interpolations sont proposées en julia permettant d'insérer les contenus de variables ou les résultats d'une expression dans une nouvelle chaîne de caractères
Interpolation dans chaines de caractères
"$c $b"
"1 + 2 = $(1+2)"
julia> "$c $b"
"tit tot"
julia> "1 + 2 = $(1+2)"
"1 + 2 = 3"

La gestion des encodings.... Il y a donc 2 fonctions sizeof() et length() qui permettent respectivement d'indiquer la taille en nombre d'octets (8 bits) et le nombre de caractères d'une chaîne de caractères
Gestion de l'utf8
s = " x  y"
sizeof(s)
length(s)
julia> s = " x  y"
"∀ x ∃ y"
julia> sizeof(s)
11
julia> length(s)
7

Signalons une autre spécifité du julia qui pour des raisons de cohérences mathématiques, l'opérateur pour coller des chaînes de caractères ensemble est l'opérateur *
Concaténation des chaines de caractères
b*c
"chaîne1" * "chaîne2" * "chaîne3"
"chaine"^3
julia> b*c
"tottit"
julia> "chaîne1" * "chaîne2" * "chaîne3"
"chaîne1chaîne2chaîne3"
julia> "chaine"^3
"chainechainechaine"

Les triple quotes servent à créer une chaîne de caractères sur plusieurs lignes avec interpolation.
Chaine de caractères multiligne
d="""
ceci permet d'écrire 
sur plusieurs lignes
avec $("inter" * "polation")
"""
julia> d="""
       ceci permet d'écrire 
       sur plusieurs lignes
       avec $("inter" * "polation")
       """
"ceci permet d'écrire 
sur plusieurs lignes
avec interpolation
"

Même si un symbole (Symbol) n'est pas directement une chaîne de caractères et que son utilité est plus directe dans le contexte de metapromamming, il est souvent utilisé en julia (comme le ruby) pour jouer le rôles d'identifiants.
Les symboles
:toto
typeof(:toto)
Symbol("toto") == :toto
julia> :toto
:toto
julia> typeof(:toto)
Symbol
julia> Symbol("toto") == :toto
true

Les types composites (i.e. non atomiques) de base

  les tableaux (Array), vecteur (Vector), matrices (Matrix)

L'une des structures de base dans un langage de programmation est la structure de tableaux (Array). Avec la notion de paramètre de type, julia permet de préciser dans le type la nature des éléments et la dimension Array{T,dim}. Le type vecteur Vector{T} est un alias de Array{T,1} et le type matrice Matrix{T} est un alias de Array{T,2}.
Les tableaux (Array) à 1 dimension (alias Vector)
strs=["apples", "bananas", "pineapples"]
julia> strs=["apples", "bananas", "pineapples"]
3-element Vector{String}:
 "apples"
 "bananas"
 "pineapples"

Les tableaux (Array) à 2 dimensions (alias Matrix)
[[1 2 3];[3 4 5]]
mat=[1 2 3; 4 5 6]
mat2=[[1;2] [3;4] [5;6]]
mat2'
transpose(mat2)
[[1 2] [3 4] [5 6]] # Attention
mat3=reshape(1:15,3,5)
mat[2]
mat[2,3]
mat[1:2,2:3]
mat[1:2,[1,end]]
mat[:,[2,2,end]]
mat[[1:3;4;3:4]]
mat3[1:2,[1:2;2:-1:1;end]]
mat3[1:2,union(1:2,2:end)]
mat * mat2
mat * mat2'
mat4=reshape(1:6,2,3)
mat4 * mat2'
julia> [[1 2 3];[3 4 5]]
2×3 Matrix{Int64}:
 1  2  3
 3  4  5
julia> mat=[1 2 3; 4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6
julia> mat2=[[1;2] [3;4] [5;6]]
2×3 Matrix{Int64}:
 1  3  5
 2  4  6
julia> mat2'
3×2 adjoint(::Matrix{Int64}) with eltype Int64:
 1  2
 3  4
 5  6
julia> transpose(mat2)
3×2 transpose(::Matrix{Int64}) with eltype Int64:
 1  2
 3  4
 5  6
julia> [[1 2] [3 4] [5 6]] # Attention
1×6 Matrix{Int64}:
 1  2  3  4  5  6
julia> mat3=reshape(1:15,3,5)
3×5 reshape(::UnitRange{Int64}, 3, 5) with eltype Int64:
 1  4  7  10  13
 2  5  8  11  14
 3  6  9  12  15
julia> mat[2]
4
julia> mat[2,3]
6
julia> mat[1:2,2:3]
2×2 Matrix{Int64}:
 2  3
 5  6
julia> mat[1:2,[1,end]]
2×2 Matrix{Int64}:
 1  3
 4  6
julia> mat[:,[2,2,end]]
2×3 Matrix{Int64}:
 2  2  3
 5  5  6
julia> mat[[1:3;4;3:4]]
6-element Vector{Int64}:
 1
 4
 2
 5
 2
 5
julia> mat3[1:2,[1:2;2:-1:1;end]]
2×5 Matrix{Int64}:
 1  4  4  1  13
 2  5  5  2  14
julia> mat3[1:2,union(1:2,2:end)]
2×5 Matrix{Int64}:
 1  4  7  10  13
 2  5  8  11  14
julia> mat * mat2
ERROR: DimensionMismatch: matrix A has dimensions (2,3), matrix B has dimensions (2,3)
julia> mat * mat2'
2×2 Matrix{Int64}:
 22  28
 49  64
julia> mat4=reshape(1:6,2,3)
2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
 1  3  5
 2  4  6
julia> mat4 * mat2'
2×2 Matrix{Int64}:
 35  44
 44  56

Au risque de se répéter, julia ne repose pas sur la vectorisation pour obtenir des bonnes performances proches du C à l'inverse de langage comme R et MATLAB. Donc par défaut, les opérations vectorielles ne font pas partie du langage de base. Il introduit cependant un mécanisme de base, appelé brodcasting, permettant de vectoriser toute fonction définie au préalable sur des données atomiques.
Opérations élément par élement (broadcast)
mat4 .* mat2
0 .< [-1,2,.5] .< 1
sin.([-1,2,.5])
sin.(mat)
julia> mat4 .* mat2
2×3 Matrix{Int64}:
 1   9  25
 4  16  36
julia> 0 .< [-1,2,.5] .< 1
3-element BitVector:
 0
 0
 1
julia> sin.([-1,2,.5])
3-element Vector{Float64}:
 -0.8414709848078965
  0.9092974268256817
  0.479425538604203
julia> sin.(mat)
2×3 Matrix{Float64}:
  0.841471   0.909297   0.14112
 -0.756802  -0.958924  -0.279415

Pour finir cette présentation sommaire sur les tableaux, proposons à la volée quelques exemples d'actions communes sur les tableaux.
Actions de base
join(strs, ", ")
join(strs, ", ", " et ")
julia> join(strs, ", ")
"apples, bananas, pineapples"
julia> join(strs, ", ", " et ")
"apples, bananas et pineapples"

  Les tuples (nommées ou pas)

Les Tuples sont des structures composites immuables (read-only) et son rôle est très important comme dans le fonctionnement même du langage, notamment au niveau des définitions des arguments de fonction. Sa syntaxe extrêmement simple, sous forme de liste d'éléments composites entourés par des parenthèses, confirme cette importance.
Les tuples
t1=(12,true,"toto")

()   # tuple vide
(1)  # Attention: tuple à 1 élément?
(1,) # Non! Plutôt comme cela!
t1[2]
t1[2:end]
a,b,c=t1
b
julia> t1=(12,true,"toto")
(12, true, "toto")
julia> 
julia> ()   # tuple vide
()
julia> (1)  # Attention: tuple à 1 élément?
1
julia> (1,) # Non! Plutôt comme cela!
(1,)
julia> t1[2]
true
julia> t1[2:end]
(true, "toto")
julia> a,b,c=t1
(12, true, "toto")
julia> b
true

De base les objets de type Tuple ne sont pas nommés et dans les premières versions de julia, les tuples nommées NamedTuple n'étaient tout au mieux proposées que dans un package extérieur.
Les tuples nommées
t2=(num=12,man=true,first="toto")
t2.first
t2.fi # Pas comme en R, extraction par nom via $ avec une liste nommée
t2[1]
t2[end]
t2[1:2]
julia> t2=(num=12,man=true,first="toto")
(num = 12, man = true, first = "toto")
julia> t2.first
"toto"
julia> t2.fi # Pas comme en R, extraction par nom via $ avec une liste nommée
ERROR: type NamedTuple has no field fi
julia> t2[1]
12
julia> t2[end]
"toto"
julia> t2[1:2]
ERROR: MethodError: no method matching getindex(::NamedTuple{(:num, :man, :first), Tuple{Int64, Bool, String}}, ::UnitRange{Int64})

Closest candidates are:
  getindex(::NamedTuple, !Matched::Int64)
   @ Base namedtuple.jl:136
  getindex(::NamedTuple, !Matched::Symbol)
   @ Base namedtuple.jl:137
  getindex(::NamedTuple, !Matched::Tuple{Vararg{Symbol}})
   @ Base namedtuple.jl:138
  ...

  Types struct et mutable struct

Struct Type
abstract type AbstractHuman end

mutable struct Human <: AbstractHuman
    first::String
    last::String
end

struct Worker <: AbstractHuman
    first::String
    last::String
    job::String
end

function Base.show(io::IO, man::Worker)
    print(io, "Worker ", convert(Human, man))
    print(io, " and my job is ", man.job)
end

Base.show(io::IO, man::AbstractHuman) = print(io, "Human: my name is ", man.first, " ", man.last)


Human("James", "Bond")

convert(::Type{Human}, man::Worker) = Human(man.first, man.last)
Worker(first::String, last::String) = Worker(first, last, "Secret Agent")

Worker("James", "Bond")

AbstractHuman[Human("R","D"), Worker("James", "Bond")]
Type{Human} <: DataType
julia> abstract type AbstractHuman end
julia> 
julia> mutable struct Human <: AbstractHuman
           first::String
           last::String
       end
julia> 
julia> struct Worker <: AbstractHuman
           first::String
           last::String
           job::String
       end
julia> 
julia> function Base.show(io::IO, man::Worker)
           print(io, "Worker ", convert(Human, man))
           print(io, " and my job is ", man.job)
       end
julia> 
julia> Base.show(io::IO, man::AbstractHuman) = print(io, "Human: my name is ", man.first, " ", man.last)
julia> 
julia> 
julia> Human("James", "Bond")
Human: my name is James Bond
julia> 
julia> convert(::Type{Human}, man::Worker) = Human(man.first, man.last)
convert (generic function with 1 method)
julia> Worker(first::String, last::String) = Worker(first, last, "Secret Agent")
Worker
julia> 
julia> Worker("James", "Bond")
Worker Human: my name is James Bond and my job is Secret Agent
julia> 
julia> AbstractHuman[Human("R","D"), Worker("James", "Bond")]
2-element Vector{AbstractHuman}:
 Human: my name is R D
 Worker Human: my name is James Bond and my job is Secret Agent
julia> Type{Human} <: DataType
true

Un peu d'instrospection de struct
Introspection de struct
mutable struct Worker2 <: AbstractHuman
    being::Human
    job::String
end

function Base.show(io::IO, man::Worker2)
    print(io, "Worker2 ", man.being)
    print(io, " and my job is ", man.job)
end

Base.getproperty(worker::Worker2, key::Symbol) = key in [:job, :being] ? getfield(worker, key) : getfield(getfield(worker, :being), key)

jb = Worker2(Human("James", "Bond"), "Secret Agent")

fieldnames(typeof(jb))
getfield(jb, :first)
getfield(jb, :job)
## But
getproperty(jb, :first)
getproperty(jb, :job)
## In a user mode
jb.first
jb.last
jb.job
jb.being

function Base.setproperty!(worker::Worker2, key::Symbol, v)
    if key in [:job, :being]
        setfield!(worker, key, v)
    else 
        setfield!(getfield(worker, :being), key, v)
    end
end
## Thanks to the above overloaded method, 
jb.first = "Remy"
jb.job = "DJ"

jb
julia> mutable struct Worker2 <: AbstractHuman
           being::Human
           job::String
       end
julia> 
julia> function Base.show(io::IO, man::Worker2)
           print(io, "Worker2 ", man.being)
           print(io, " and my job is ", man.job)
       end
julia> 
julia> Base.getproperty(worker::Worker2, key::Symbol) = key in [:job, :being] ? getfield(worker, key) : getfield(getfield(worker, :being), key)
julia> 
julia> jb = Worker2(Human("James", "Bond"), "Secret Agent")
Worker2 Human: my name is James Bond and my job is Secret Agent
julia> 
julia> fieldnames(typeof(jb))
(:being, :job)
julia> getfield(jb, :first)
ERROR: type Worker2 has no field first
julia> getfield(jb, :job)
"Secret Agent"
julia> ## But
julia> getproperty(jb, :first)
"James"
julia> getproperty(jb, :job)
"Secret Agent"
julia> ## In a user mode
julia> jb.first
"James"
julia> jb.last
"Bond"
julia> jb.job
"Secret Agent"
julia> jb.being
Human: my name is James Bond
julia> 
julia> function Base.setproperty!(worker::Worker2, key::Symbol, v)
           if key in [:job, :being]
               setfield!(worker, key, v)
           else 
               setfield!(getfield(worker, :being), key, v)
           end
       end
julia> ## Thanks to the above overloaded method, 
julia> jb.first = "Remy"
"Remy"
julia> jb.job = "DJ"
"DJ"
julia> 
julia> jb
Worker2 Human: my name is Remy Bond and my job is DJ

  Les dictionnaires Dict

Aussi appelés Hash dans d'autres langages, les valeurs de type Dict permmettent d'associer à une clé une unique valeur.
Les dictionnaires (Dict)
d=Dict(:a => "a", :b => "b")
d[:a]
keys(d)
values(d)
julia> d=Dict(:a => "a", :b => "b")
Dict{Symbol, String} with 2 entries:
  :a => "a"
  :b => "b"
julia> d[:a]
"a"
julia> keys(d)
KeySet for a Dict{Symbol, String} with 2 entries. Keys:
  :a
  :b
julia> values(d)
ValueIterator for a Dict{Symbol, String} with 2 entries. Values:
  "a"
  "b"

Une originalité en julia est que pour les clés définies comme des Tuples, il n'est pas nécessaire pour extraire un élément de saisir les parenthèses.
Extraction Dict par Tuple
d2=Dict((:a,1) => "a", (:b,2) => "b")
d2[(:a,1)]
d2[:a,1]
julia> d2=Dict((:a,1) => "a", (:b,2) => "b")
Dict{Tuple{Symbol, Int64}, String} with 2 entries:
  (:b, 2) => "b"
  (:a, 1) => "a"
julia> d2[(:a,1)]
"a"
julia> d2[:a,1]
"a"

Type de Type

Type de Type
function g(x, typ::Type{Float64})
    if x isa typ
        "c'est bien un réel"
    else
        "ce n'est pas un réel"
    end
end

function g(x, typ::Type{Int64})
    if x isa typ
        "c'est bien un entier"
    else
        "ce n'est pas un entier"
    end
end

## Comme la valeur de typ est constant 
## pas besoin de le spécifier 
## et utiliser sa valeur à l'intérieur de la méthode
function f(x, ::Type{Float64})
    if x isa Float64
        "c'est bien un réel"
    else
        "ce n'est pas un réel"
    end
end

function f(x, ::Type{Int64})
    if x isa Int64
        "c'est bien un entier"
    else
        "ce n'est pas un entier"
    end
end

g(1, Float64)
g(1.0, Float64)
g(1, Int64)
g(1.0, Int64)

f(1, Float64)
f(1.0, Float64)
f(1, Int64)
f(1.0, Int64)

Float64 isa Type{Float64}
Int64 isa Type{Int64}

typeof(Float64)
typeof(Int64)

Float64 isa DataType
Float64 isa Type

supertype(Type{Float64})
supertypes(Type{Float64})
Type{Float64} <: DataType
julia> function g(x, typ::Type{Float64})
           if x isa typ
               "c'est bien un réel"
           else
               "ce n'est pas un réel"
           end
       end
g (generic function with 2 methods)
julia> 
julia> function g(x, typ::Type{Int64})
           if x isa typ
               "c'est bien un entier"
           else
               "ce n'est pas un entier"
           end
       end
g (generic function with 2 methods)
julia> 
julia> ## Comme la valeur de typ est constant 
julia> ## pas besoin de le spécifier 
julia> ## et utiliser sa valeur à l'intérieur de la méthode
julia> function f(x, ::Type{Float64})
           if x isa Float64
               "c'est bien un réel"
           else
               "ce n'est pas un réel"
           end
       end
f (generic function with 2 methods)
julia> 
julia> function f(x, ::Type{Int64})
           if x isa Int64
               "c'est bien un entier"
           else
               "ce n'est pas un entier"
           end
       end
f (generic function with 2 methods)
julia> 
julia> g(1, Float64)
"ce n'est pas un réel"
julia> g(1.0, Float64)
"c'est bien un réel"
julia> g(1, Int64)
"c'est bien un entier"
julia> g(1.0, Int64)
"ce n'est pas un entier"
julia> 
julia> f(1, Float64)
"ce n'est pas un réel"
julia> f(1.0, Float64)
"c'est bien un réel"
julia> f(1, Int64)
"c'est bien un entier"
julia> f(1.0, Int64)
"ce n'est pas un entier"
julia> 
julia> Float64 isa Type{Float64}
true
julia> Int64 isa Type{Int64}
true
julia> 
julia> typeof(Float64)
DataType
julia> typeof(Int64)
DataType
julia> 
julia> Float64 isa DataType
true
julia> Float64 isa Type
true
julia> 
julia> supertype(Type{Float64})
Any
julia> supertypes(Type{Float64})
(Type{Float64}, Any)
julia> Type{Float64} <: DataType
true

Eléments de programmation

  Fonctions

Avant même de rentrer dans le coeur de julia (et le mécanisme de "multiple dispatching"), nous pouvons lors de notre première visite avoir l'illusion que Julia est un langage fonctionnel (ni plus ni moins, sans tenir compte que de son atout "maître" qu'est le "multiple dispatching"). Notons d'ailleurs qu'un niveau utilisateur "pur" (i.e. qui n'écrira aucune fonction) n'a aucune raison de se soucier de connaître le mécanisme "multiple dispatching". Comme d'habitude, la documentation officielle sur les fonctions (Functions) est très bien faite et nous ne reprenons ici que quelques éléments.
Voilà 3 manières de déclarer des fonctions en Julia :
Déclarations de fonctions
function somme(x,y)
	x + y
end
somme2(x,y) = x+y
somme3 = (x,y) -> x+y
somme(2,3), somme2(2,3), somme3(2,3)
julia> function somme(x,y)
         x + y
       end
somme (generic function with 1 method)
julia> somme2(x,y) = x+y
somme2 (generic function with 1 method)
julia> somme3 = (x,y) -> x+y
#1409 (generic function with 1 method)
julia> somme(2,3), somme2(2,3), somme3(2,3)
(5, 5, 5)

Nous n'expliquerons qu'un peu plus tard ce que signifie précisément le terme de "fonction générique" (generic function), par ailleurs connu par les utilisateurs de R. Notons tout de suite que les 2 premières formes (somme et somme2) sont les déclarations usuelles de fonctions quand la troisième (somme3) correspond à l'affectation d'une fonction anonyme à une variable Julia (ici somme3).
Comme Julia se veut être un langage numérique proche des mathématiques, il nous propose l'utilisation des caractères Unicode dans le noms de fonctions.
Caractères unicodes utilisables
function Σ(x,y)
	x + y
end
Σ2(x,y) = x+y
Σ3 = (x,y) -> x+y
Σ(2,3),Σ2(2,3),Σ3(2,3)
julia> function Σ(x,y)
         x + y
       end
Σ (generic function with 2 methods)
julia> Σ2(x,y) = x+y
Σ2 (generic function with 1 method)
julia> Σ3 = (x,y) -> x+y
#1411 (generic function with 1 method)
julia> Σ(2,3),Σ2(2,3),Σ3(2,3)
(5, 5, 5)

Pour aller plus loin, la lecture de la documentation officielle sur les fonctions est excellente car elle met en avant quelques trésors proposés par Julia:
  • return et contrainte de type (annotation) sur l'élément retourné.
  • Les arguments variables en déclaration et appel
  • Les arguments avec valeurs par défaut
  • Les arguments "clés" (keyword)
  • L'excellente syntaxe do
  • La syntaxe dot vectorielle (broadcasting)
De manière plus avancée, soulignons les points suivants:
  • Compositon de fonctions
    Composition de fonctions
    (sin ∘ cos)(10)
    (sin ∘ cos).(1:3:10)
    
    julia> (sin ∘ cos)(10)
    -0.7440230792707043
    julia> (sin ∘ cos).(1:3:10)
    4-element Vector{Float64}:
      0.5143952585235492
     -0.6080830096407656
      0.6844887989926141
     -0.7440230792707043
    
    
  • Définition de fonction partielle à partir d'opérateur ou de fonction à 2 paramètres
    Base.Fix1 et Base.Fix2
    <(2)(1)
    <(1,2)
    typeof(<(2))
    Base.Fix2(<,2) == <(2)
    Base.Fix2(<,2)(1)
    Base.Fix1(<,1)
    Base.Fix1(<,1)(2)
    in(1:10)
    in(1:10)(3)
    f(x,y)=x-y
    Base.Fix1(f,2)
    Base.Fix1(f,2)(3)
    
    julia> <(2)(1)
    true
    julia> <(1,2)
    true
    julia> typeof(<(2))
    Base.Fix2{typeof(<), Int64}
    julia> Base.Fix2(<,2) == <(2)
    true
    julia> Base.Fix2(<,2)(1)
    true
    julia> Base.Fix1(<,1)
    (::Base.Fix1{typeof(<), Int64}) (generic function with 1 method)
    julia> Base.Fix1(<,1)(2)
    true
    julia> in(1:10)
    (::Base.Fix2{typeof(in), UnitRange{Int64}}) (generic function with 1 method)
    julia> in(1:10)(3)
    true
    julia> f(x,y)=x-y
    f (generic function with 1 method)
    julia> Base.Fix1(f,2)
    (::Base.Fix1{typeof(f), Int64}) (generic function with 1 method)
    julia> Base.Fix1(f,2)(3)
    -1
    
    
  • L'usage de ... (voir slurping et splatting)
    Slurp et Splat
    Σ(x...) = sum(x) # slurping pour déclaration de fonction avec arguments variables
    Σ(1,3,2,4)
    Σ(1,3,2,4,5)
    g(a,b,c) = a + b + c
    g([1,2,3]...) # splatting pour appel de fonction en déstructurant un vecteur
    g((1,2,3)...) # splatting pour appel de fonction en déstructurant un tuple
    g((1:3)...) # splatting pour appel de fonction en déstructurant un range
    g((1:2:5)...)
    ## La fonction splat permet d'appliquer une fonction à une structure composite en la déstructurant
    splat(g)
    typeof(splat(g))
    splat(g)([1,2,3]) # équivalent à g([1,2,3]...)
    splat(g)((1:2:5)) # équivalent à g((1:2:5)...)
    splat(Σ)
    splat(Σ)([1,2,3,4]) # équivalent à Σ(1,3,2,4)
    splat(Σ)((1,2,3,4,5)) # équivalent à Σ(1,3,2,4,5)
    
    julia> Σ(x...) = sum(x) # slurping pour déclaration de fonction avec arguments variables
    Σ (generic function with 2 methods)
    julia> Σ(1,3,2,4)
    10
    julia> Σ(1,3,2,4,5)
    15
    julia> g(a,b,c) = a + b + c
    g (generic function with 1 method)
    julia> g([1,2,3]...) # splatting pour appel de fonction en déstructurant un vecteur
    6
    julia> g((1,2,3)...) # splatting pour appel de fonction en déstructurant un tuple
    6
    julia> g((1:3)...) # splatting pour appel de fonction en déstructurant un range
    6
    julia> g((1:2:5)...)
    9
    julia> ## La fonction splat permet d'appliquer une fonction à une structure composite en la déstructurant
    julia> splat(g)
    splat(g)
    julia> typeof(splat(g))
    Base.Splat{typeof(g)}
    julia> splat(g)([1,2,3]) # équivalent à g([1,2,3]...)
    6
    julia> splat(g)((1:2:5)) # équivalent à g((1:2:5)...)
    9
    julia> splat(Σ)
    splat(Σ)
    julia> splat(Σ)([1,2,3,4]) # équivalent à Σ(1,3,2,4)
    10
    julia> splat(Σ)((1,2,3,4,5)) # équivalent à Σ(1,3,2,4,5)
    15
    
    

  Les instructions de type for

Le langage julia même s'il est utilisé avec la même apparente facilité que les langages interprétés tels que R et python est un langage compilé rétablissant l'usage des boucles for.
  • Itérateurs: comme rappelé en Interfaces, un bloc de code de la forme
    for i in iter   # or  "for i = iter"
        # body
    end
    
    est (astucieusement) remplacé (avant la compilation JIT) par :
    next = iterate(iter)
    while next !== nothing
        (i, state) = next
        # body
        next = iterate(iter, state)
    end
    
    Donc, on comprend que ce tour de magie transforme tout type pour lequel iterate est défini sous les 2 formes initialisation et itération en un itérateur applicable à une boucle for.
    Itérateur pour boucle for
    # Vecteur
    v=[1,3,2]
    iterate(v),iterate(v,2),iterate(v,3),iterate(v,4)
    for e = v; print("$e "); end; println()
    # Matrice
    m=[1 2; 3 4]
    iterate(m),iterate(m,2),iterate(m,3),iterate(m,4),iterate(m,5)
    for e = m; print("$e "); end; println()
    # Vecteur ? Non ! C'est un Range
    iter=5:10
    typeof(iter)
    iterate(iter),iterate(iter,5),iterate(iter,9),iterate(iter,10)
    for e = iter; print("$e "); end; println()
    collect(iter)
    vcat(iter) # astuce équivalente à collect(iter)
    [iter;]    # la même chose que vcat(iter)
    iter.^2
    iter2=1:2:5
    typeof(iter2)
    iterate(iter2), iterate(iter2,1), iterate(iter2,3), iterate(iter2,5)
    for e = iter2; print("$e "); end; println()
    iter2.^2
    ## Pour les experts
    methods(iterate,[Array])
    methods(iterate,[UnitRange,StepRange])
    UnitRange <: OrdinalRange, StepRange <: OrdinalRange
    supertype(UnitRange),supertype(StepRange)
    supertype(supertype(UnitRange))
    
    julia> # Vecteur
    julia> v=[1,3,2]
    3-element Vector{Int64}:
     1
     3
     2
    julia> iterate(v),iterate(v,2),iterate(v,3),iterate(v,4)
    ((1, 2), (3, 3), (2, 4), nothing)
    julia> for e = v; print("$e "); end; println()
    1 3 2 
    julia> # Matrice
    julia> m=[1 2; 3 4]
    2×2 Matrix{Int64}:
     1  2
     3  4
    julia> iterate(m),iterate(m,2),iterate(m,3),iterate(m,4),iterate(m,5)
    ((1, 2), (3, 3), (2, 4), (4, 5), nothing)
    julia> for e = m; print("$e "); end; println()
    1 3 2 4 
    julia> # Vecteur ? Non ! C'est un Range
    julia> iter=5:10
    5:10
    julia> typeof(iter)
    UnitRange{Int64}
    julia> iterate(iter),iterate(iter,5),iterate(iter,9),iterate(iter,10)
    ((5, 5), (6, 6), (10, 10), nothing)
    julia> for e = iter; print("$e "); end; println()
    5 6 7 8 9 10 
    julia> collect(iter)
    6-element Vector{Int64}:
      5
      6
      7
      8
      9
     10
    julia> vcat(iter) # astuce équivalente à collect(iter)
    6-element Vector{Int64}:
      5
      6
      7
      8
      9
     10
    julia> [iter;]    # la même chose que vcat(iter)
    6-element Vector{Int64}:
      5
      6
      7
      8
      9
     10
    julia> iter.^2
    6-element Vector{Int64}:
      25
      36
      49
      64
      81
     100
    julia> iter2=1:2:5
    1:2:5
    julia> typeof(iter2)
    StepRange{Int64, Int64}
    julia> iterate(iter2), iterate(iter2,1), iterate(iter2,3), iterate(iter2,5)
    ((1, 1), (3, 3), (5, 5), nothing)
    julia> for e = iter2; print("$e "); end; println()
    1 3 5 
    julia> iter2.^2
    3-element Vector{Int64}:
      1
      9
     25
    julia> ## Pour les experts
    julia> methods(iterate,[Array])
    # 1 method for generic function "iterate" from Base:
     [1] iterate(A::Array)
         @ array.jl:893
    julia> methods(iterate,[UnitRange,StepRange])
    # 1 method for generic function "iterate" from Base:
     [1] iterate(r::OrdinalRange{T}, i) where T
         @ range.jl:889
    julia> UnitRange <: OrdinalRange, StepRange <: OrdinalRange
    (true, true)
    julia> supertype(UnitRange),supertype(StepRange)
    (AbstractUnitRange{T} where T<:Real, OrdinalRange)
    julia> supertype(supertype(UnitRange))
    OrdinalRange{T, T} where T<:Real
    
    
  • map: appliquer une fonction à une structure composite (Array, Tuple, Range, ...)
    map et do
    map(x -> x + 2,1:3)
    map(x -> x + 2,[1,2,3])
    map(x -> x + 2,(1,2,3))
    map(x -> x + 2,Base.OneTo(3))
    map(1:3) do x
        x + 2
    end
    map(x -> x + 2,1:2:5)
    map(x -> x + 2,[1 2;3 4])
    map((x,y) -> x + y, 1:3, 4:8)
    map(1:3, 4:8) do x,y
        x + y
    end
    # L'équivalent avec boucle for
    res = zeros(Int64,3)
    for (i,(x,y))=enumerate(zip(1:3, 4:6))
        res[i] = x + y
    end
    res
    
    julia> map(x -> x + 2,1:3)
    3-element Vector{Int64}:
     3
     4
     5
    julia> map(x -> x + 2,[1,2,3])
    3-element Vector{Int64}:
     3
     4
     5
    julia> map(x -> x + 2,(1,2,3))
    (3, 4, 5)
    julia> map(x -> x + 2,Base.OneTo(3))
    3-element Vector{Int64}:
     3
     4
     5
    julia> map(1:3) do x
               x + 2
           end
    3-element Vector{Int64}:
     3
     4
     5
    julia> map(x -> x + 2,1:2:5)
    3-element Vector{Int64}:
     3
     5
     7
    julia> map(x -> x + 2,[1 2;3 4])
    2×2 Matrix{Int64}:
     3  4
     5  6
    julia> map((x,y) -> x + y, 1:3, 4:8)
    3-element Vector{Int64}:
     5
     7
     9
    julia> map(1:3, 4:8) do x,y
               x + y
           end
    3-element Vector{Int64}:
     5
     7
     9
    julia> # L'équivalent avec boucle for
    julia> res = zeros(Int64,3)
    3-element Vector{Int64}:
     0
     0
     0
    julia> for (i,(x,y))=enumerate(zip(1:3, 4:6))
               res[i] = x + y
           end
    julia> res
    3-element Vector{Int64}:
     5
     7
     9
    
    
  • Broadcasting: est un moyen qui permet de vectoriser toutes fonctions (et bien sûr même celles définies par l'utilisateur).
    broadcasting @.
    sin([1,3,2])
    sin(1:3)
    sin.([1,3,2])
    sin.(1:3)
    sin.(1:3) .+ 3 .* (2:4) # un seul for #rmq: parenthèses autour de 2:4 est requis
    @. sin(1:3) + 3 * (2:4) # derrière la scène, voilà ce qui est appelé lors de l'exécution à la ligne précdente
    [1,3,2] + [3,4,5]
    1:3 + 3:5
    (1:3) + (3:5)
    collect(1:3 + 3:5)
    1:3 .+ 3:5
    (1:3) .+ (3:5)
    [1,3,2] + (2:4)
    [1,3,2] .+ (2:4)
    1:3 * 3:5
    (1:3) * (3:5)
    (1:3) .* (3:5)
    
    julia> sin([1,3,2])
    ERROR: MethodError: no method matching sin(::Vector{Int64})
    
    Closest candidates are:
      sin(!Matched::T) where T<:Union{Float32, Float64}
       @ Base special/trig.jl:29
      sin(!Matched::LinearAlgebra.UniformScaling)
       @ LinearAlgebra ~/tools/install/julia-1.9.3/share/julia/stdlib/v1.9/LinearAlgebra/src/uniformscaling.jl:173
      sin(!Matched::LinearAlgebra.Hermitian{var"#s971", S} where {var"#s971"<:Complex, S<:(AbstractMatrix{<:var"#s971"})})
       @ LinearAlgebra ~/tools/install/julia-1.9.3/share/julia/stdlib/v1.9/LinearAlgebra/src/symmetric.jl:732
      ...
    
    julia> sin(1:3)
    ERROR: MethodError: no method matching sin(::UnitRange{Int64})
    
    Closest candidates are:
      sin(!Matched::T) where T<:Union{Float32, Float64}
       @ Base special/trig.jl:29
      sin(!Matched::LinearAlgebra.UniformScaling)
       @ LinearAlgebra ~/tools/install/julia-1.9.3/share/julia/stdlib/v1.9/LinearAlgebra/src/uniformscaling.jl:173
      sin(!Matched::LinearAlgebra.Hermitian{var"#s971", S} where {var"#s971"<:Complex, S<:(AbstractMatrix{<:var"#s971"})})
       @ LinearAlgebra ~/tools/install/julia-1.9.3/share/julia/stdlib/v1.9/LinearAlgebra/src/symmetric.jl:732
      ...
    
    julia> sin.([1,3,2])
    3-element Vector{Float64}:
     0.8414709848078965
     0.1411200080598672
     0.9092974268256817
    julia> sin.(1:3)
    3-element Vector{Float64}:
     0.8414709848078965
     0.9092974268256817
     0.1411200080598672
    julia> sin.(1:3) .+ 3 .* (2:4) # un seul for #rmq: parenthèses autour de 2:4 est requis
    3-element Vector{Float64}:
      6.841470984807897
      9.909297426825681
     12.141120008059866
    julia> @. sin(1:3) + 3 * (2:4) # derrière la scène, voilà ce qui est appelé lors de l'exécution à la ligne précdente
    3-element Vector{Float64}:
      6.841470984807897
      9.909297426825681
     12.141120008059866
    julia> [1,3,2] + [3,4,5]
    3-element Vector{Int64}:
     4
     7
     7
    julia> 1:3 + 3:5
    1:6:1
    julia> (1:3) + (3:5)
    4:2:8
    julia> collect(1:3 + 3:5)
    1-element Vector{Int64}:
     1
    julia> 1:3 .+ 3:5
    1:6:1
    julia> (1:3) .+ (3:5)
    4:2:8
    julia> [1,3,2] + (2:4)
    3-element Vector{Int64}:
     3
     6
     6
    julia> 1,3,2] .+ (2:4)
    3-element Vector{Int64}:
     3
     6
     6
    julia> 1:3 * 3:5
    1:9:1
    julia> (1:3) * (3:5)
    ERROR: MethodError: no method matching *(::UnitRange{Int64}, ::UnitRange{Int64})
    
    Closest candidates are:
      *(::Any, ::Any, !Matched::Any, !Matched::Any...)
       @ Base operators.jl:578
      *(!Matched::LinearAlgebra.LQ{TA, S, C} where {S<:AbstractMatrix{TA}, C<:AbstractVector{TA}}, ::AbstractVecOrMat{TB}) where {TA, TB}
       @ LinearAlgebra ~/tools/install/julia-1.9.3/share/julia/stdlib/v1.9/LinearAlgebra/src/lq.jl:198
      *(::AbstractVector, !Matched::LinearAlgebra.AbstractRotation)
       @ LinearAlgebra ~/tools/install/julia-1.9.3/share/julia/stdlib/v1.9/LinearAlgebra/src/givens.jl:19
      ...
    
    julia> (1:3) .* (3:5)
    3-element Vector{Int64}:
      3
      8
     15
    
    
  • Array comprehension
    Array comprehension
    [sin(x) for x=1:3]
    [sin(x+y) for x=1:3 for y=2:4] # double boucles for
    map(1:3,2:4) do x,y; sin(x+y); end # A ne pas confondre
    [sin(x+y) for (x,y)=zip(1:3,2:4)] # utiliser zip pour avoir l'équivalent en comprehension du map (précédent)
    [sin(x+y) for x=1:3, y=2:4] # pour créer une matrice
    [(x, y) for x=1:3, y=2:4]
    # un peu d'explication sur le fonctionnement
    (sin(x+y) for x=1:3 for y=2:4)
    collect(sin(x+y) for x=1:3 for y=2:4) # équivalent à [sin(x+y) for x=1:3 for y=2:4]
    ((x, y) for x=1:3, y=2:4) 
    collect((x, y) for x=1:3, y=2:4) # équivalent à [(x, y) for x=1:3, y=2:4]
    
    julia> [sin(x) for x=1:3]
    3-element Vector{Float64}:
     0.8414709848078965
     0.9092974268256817
     0.1411200080598672
    julia> [sin(x+y) for x=1:3 for y=2:4] # double boucles for
    9-element Vector{Float64}:
      0.1411200080598672
     -0.7568024953079282
     -0.9589242746631385
     -0.7568024953079282
     -0.9589242746631385
     -0.27941549819892586
     -0.9589242746631385
     -0.27941549819892586
      0.6569865987187891
    julia> map(1:3,2:4) do x,y; sin(x+y); end # A ne pas confondre
    3-element Vector{Float64}:
      0.1411200080598672
     -0.9589242746631385
      0.6569865987187891
    julia> [sin(x+y) for (x,y)=zip(1:3,2:4)] # utiliser zip pour avoir l'équivalent en comprehension du map (précédent)
    3-element Vector{Float64}:
      0.1411200080598672
     -0.9589242746631385
      0.6569865987187891
    julia> [sin(x+y) for x=1:3, y=2:4] # pour créer une matrice
    3×3 Matrix{Float64}:
      0.14112   -0.756802  -0.958924
     -0.756802  -0.958924  -0.279415
     -0.958924  -0.279415   0.656987
    julia> [(x, y) for x=1:3, y=2:4]
    3×3 Matrix{Tuple{Int64, Int64}}:
     (1, 2)  (1, 3)  (1, 4)
     (2, 2)  (2, 3)  (2, 4)
     (3, 2)  (3, 3)  (3, 4)
    julia> # un peu d'explication sur le fonctionnement
    julia> (sin(x+y) for x=1:3 for y=2:4)
    Base.Iterators.Flatten{Base.Generator{UnitRange{Int64}, var"#1446#1447"}}(Base.Generator{UnitRange{Int64}, var"#1446#1447"}(var"#1446#1447"(), 1:3))
    julia> collect(sin(x+y) for x=1:3 for y=2:4) # équivalent à [sin(x+y) for x=1:3 for y=2:4]
    9-element Vector{Float64}:
      0.1411200080598672
     -0.7568024953079282
     -0.9589242746631385
     -0.7568024953079282
     -0.9589242746631385
     -0.27941549819892586
     -0.9589242746631385
     -0.27941549819892586
      0.6569865987187891
    julia> ((x, y) for x=1:3, y=2:4) 
    Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}, var"#1453#1454"}(var"#1453#1454"(), Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:3, 2:4)))
    julia> collect((x, y) for x=1:3, y=2:4) # équivalent à [(x, y) for x=1:3, y=2:4]
    3×3 Matrix{Tuple{Int64, Int64}}:
     (1, 2)  (1, 3)  (1, 4)
     (2, 2)  (2, 3)  (2, 4)
     (3, 2)  (3, 3)  (3, 4)
    
    

  Les instructions de programmation

La documentation officielle est ici. Les principaux points abordés sont les suivants :
  • Les instructions regroupés begin-end et (;)
  • Les conditions if-elseif-else et la condition ternaire ? chaînable
  • Les conditions-évaluations circuits-courts comme un possible remplacement de switch (comme en R et python)
  • Le contrôle d'erreur (try-catch et throw)

Programmations orientées objets

Afin de mieux appréhender le type de programmation multiple dispatching qui est LA brique de base de julia, nous proposons ici, en guise de comparaison, de rappeler la programmation (plus conventionnelle) orientée objet (OOP) utilisée par python (mais aussi par C++, ruby, ...). Pour compléter cette comparaison, nous proposons les 3 différents types de programmation OOP utilisables dans le R. Soulignons toutefois que le mode OOP de base dans le R (appelé S3) est de type simple dispatching (et s'apparente dans sa philosophie au mode de programmation OOP de python) et nous permettra de bien faire la différence avec le mode plus complet de multiple dispatching (apparaissant aussi dans le R sous le nom S4).

Programmation avec structure encapsulée dans une déclaration de classe

C'est le type de programmation orientée objet le plus connu qui est utilisée dans des langages de programmation comme C++, java, python, ruby, ... Comme python est un langage candidat comme une alternative au R, illustrons ce principe sur un exemple. La déclaration d'une classe (ici Point) consiste à proposer dans une même structure (identifiée ici par le mot-clé class). Ce type de travail correspond à une tâche allouée à un développeur qui propose des outils à des utilisateurs (généralement fournis par le développement d'un package ou librairie).

Version python

Déclaration Classe Point
class Point(object):
    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y
        
    def translate(self, dx, dy):
        self.x += dx
        self.y += dy
        
    def __str__(self):
        return("Point: ({:.2f}, {:.2f})".format(self.x, self.y))
>>> class Point(object):
...     def __init__(self, x=0.0, y=0.0):
...         self.x = x
...         self.y = y
... 
...     def translate(self, dx, dy):
...         self.x += dx
...         self.y += dy
... 
...     def __str__(self):
...         return("Point: ({:.2f}, {:.2f})".format(self.x, self.y))
Une fonction encapsulée dans une structure de classe s'appelle une méthode. En python, on les reconnaît facilement car le premier argument est le self qui correspondra à l'objet qui sera généré lors d'une instanciation (i.e. création d'un objet en mémoire). La première méthode __init__() est la méthode qui sert à générér l'objet lors d'une instanciation. Une fois créé, un objet, généralement enregistré dans une variable, peut appeler ses autres méthodes. La méthode __str__() est utilisée pour chaque affichage par défaut de l'objet. Illustrons cela en proposant quelques instanciations et appels de méthodes, tâches qui correspondent à ce qu'un utilisateur a besoin de manipuler.
Instanciation de la Classe Point
p1 = Point()
p1

p1.translate(3,4)
p1

p2 = Point(1.2,3)
p2
>>> p1 = Point()
>>> p1
Point: (0.00, 0.00)
>>>
>>> p1.translate(3,4)
>>> p1
Point: (6.00, 8.00)
>>>
>>> p2 = Point(1.2,3)
>>> p2
Point: (1.20, 3.00)
Nous remarquons qu'une fonction Point(), appelée constructeur, a été automatiquement construit par python lors de la déclaration de la structure de classe permettant d'instancier un objet de cette classe. Ce constructeur appelle la méthode __init__() pour finaliser la création de l'objet. Une fois créé, il peut être appliqué à l'objet enregistré dans une variable un appel à une méthode sous la forme .() comme dans l'exemple ci-dessus p1.translate(3,4)

Version R mode R6

Dans le R, la programmation orienté objet de base est celle de type S3 (décrite dans la section suivante). Il existe depuis peu le même type de programmation que celle du python appelée R6. Sans la commenter proposons les codes permettant d'obtenir la même classe Point.
Classe R6 Point
require(R6)
Point <- R6Class("Point", #membres publics
public = list(
#champs
x = 0,
y = 0,
#constructeur
initialize = function(x=0, y=0) {
	self$x <- x
	self$y <- y
},
translate = function(dx,dy) {
	self$x <- self$x + dx
	self$y <- self$y + dy
},
print = function() cat("Point: (",self$x,",",self$y,")
",sep="")
) )
R> require(R6)
R> Point <- R6Class("Point", #membres publics
+ public = list(
+ #champs
+ x = 0,
+ y = 0,
+ #constructeur
+ initialize = function(x=0, y=0) {
+ 	self$x <- x
+ 	self$y <- y
+ },
+ translate = function(dx,dy) {
+ 	self$x <- self$x + dx
+ 	self$y <- self$y + dy
+ },
+ print = function() cat("Point: (",self$x,",",self$y,")
",sep="")
+ ) )

Pour l'utilisateur R, nous obtenons des lignes de code similaires à ce qui était proposé à l'utilisateur python.

R6 instance pour Point
p1 <- Point$new()
p1

p1$translate(3,4)
p1

p2 <- Point$new(1.2,3)
p2
R> p1 <- Point$new()
R> p1
Point: (0,0)

R> p1$translate(3,4)
R> p1
Point: (3,4)

R> p2 <- Point$new(1.2,3)
R> p2
Point: (1.2,3)

Sans trop entrer dans les détails, notons au passage que la programmation de type R6 repose sur le dynamisme des objets environment de R.

R6 objet comme environment
class(p1)
is.environment(p1)
ls(p1)
R> class(p1)
[1] "Point" "R6"   
R> is.environment(p1)
[1] TRUE
R> ls(p1)
[1] "clone"      "initialize" "print"      "translate"  "x"         
[6] "y"

Programmation de type redirection simple ("Single Dispatching")

La programmation de type S3 en R, permet de reproduire la programmation orientée objet python en n'utilisant que les mécanismes de base que sont la notion d'attributs (spécifique au R) et fonctions. Elle s'appuie sur un mécanisme de redirection simple ("Single dispatching") à partir de fonction générique. C'est d'ailleurs la base du système R. Cette approche d'ailleurs permet d'accorder plus d'importance aux actions-type plutôt qu'aux structures de classes. Per exemple, en R, les actions-type les plus connues en R sont print(), plot(), summary().
Fonction S3 genérique
print
plot
summary
R> print
function (x, ...) 
UseMethod("print")
<bytecode: 0x55d25c74beb8>
<environment: namespace:base>
R> plot
function (x, y, ...) 
UseMethod("plot")
<bytecode: 0x55d25fc49298>
<environment: namespace:base>
R> summary
function (object, ...) 
UseMethod("summary")
<bytecode: 0x55d25e7952a0>
<environment: namespace:base>

Ces fonctions génériques se caractérisent par l'appel dans son corps de fonction de la "nébuleuse" (dans son fonctionnement mais pas dans son nom) fonction UseMethod() qui est en charge de la redirection vers la bonne méthode de la forme <action>.<classe>()<action> est le nom de l'action décrite dans l'appel de UseMethod() et <classe> est le nom de l'attribut class de l'objet appelé en premier argument de la focntion générique. Le "single dispatching" porte son nom précisément du fait que la redirection repose uniquement sur la classe du premier ("single") argument de la fonction générique. On peut alors déjà entrevoir ce que sera le "multiple dispatching".

En R, sans que cela soit une contrainte, le nom de la fonction générique est associée à une action du même nom que celle de la fonction générique. Mais ce n'est pas toujours le cas, ce qui permet facilement de proposer des alias, comme par exemple coefficients() qui est un alias de coef().

Fonction S3 générique alias
coef
coefficients
R> coef
function (object, ...) 
UseMethod("coef")
<bytecode: 0x55d25d9871a8>
<environment: namespace:stats>
R> coefficients
function (object, ...) 
UseMethod("coef")
<bytecode: 0x55d25e7af630>
<environment: namespace:stats>

Fournissons à présent le code R de type S3 qui permet de faire l'équivalent de l'exemple précédent en python en mode développeur.

Classe S3 Point
Point <- function( x=0, y=0) {
	point <- new.env() # en R, on utilise généralement une list()
	point$x <- x
	point$y<-y
	class(point) <- "Point"
	point
}
## la fonction générique associée à la tâche "translate"
translate <- function(obj,...) UseMethod("translate")

## Les méthodes de la classe Point qui ne sont que des simples fonctions
translate.Point <- function(point,dx,dy) {
	point$x <- point$x + dx
	point$y <- point$y + dy
}
print.Point <- function(point) 
	cat("Point: (",point$x,",",point$y,")
",sep="")
R> Point <- function( x=0, y=0) {
+ 	point <- new.env() # en R, on utilise généralement une list()
+ 	point$x <- x
+ 	point$y<-y
+ 	class(point) <- "Point"
+ 	point
+ }
R> ## la fonction générique associée à la tâche "translate"
R> translate <- function(obj,...) UseMethod("translate")

R> ## Les méthodes de la classe Point qui ne sont que des simples fonctions
R> translate.Point <- function(point,dx,dy) {
+ 	point$x <- point$x + dx
+ 	point$y <- point$y + dy
+ }
R> print.Point <- function(point) 
+ 	cat("Point: (",point$x,",",point$y,")
",sep="")

Notons un point essentiel : pour mimiquer le comportement dynamique des méthodes python avec l'utilisation de new.env() qui à la différence de la classique structure "statique" list permet de modifier à l'intérieur d'une méthode l'objet lui-même. Nous avons déjà remarqué que La programmation de type R6 reposait sur le dynamisque de l'objet environment.

De manière semblable, nous pouvons proposer les lignes de codes en mode utilisateur.

Instance (objet) de Point
p1 <- Point()
p1

translate(p1,3,4)
p1

p2 <-Point(1.2,3)
p2
R> p1 <- Point()
R> p1
Point: (0,0)

R> translate(p1,3,4)
R> p1
Point: (3,4)

R> p2 <-Point(1.2,3)
R> p2
Point: (1.2,3)

Programmation de type redirection multiple ("Multiple Dispatching")

Dans ses fondations, le langage julia repose très élégamment sur le mécanisme de "multiple dispatching". A titre personnel, je n'avais réellement pas très bien compris l'intérêt dans R de la programmation S4 de type "multiple dispatching" un peu aussi découragé par une syntaxe un peu lourde ainsi qu'un manque de dynamisme (comme illustré en fin de paragraphe). Grâce au langage julia, j'ai pu apprendre dans un premier temps ce concept puis en découvrir l'intérêt. En clair, le "multiple dispatching" généralise le "single dispatching" dans le sens où une fonction générique applique une action différente selon la signature complète de la fonction générique (à savoir les types de tous ses arguments). Rappelons que pour le "single dispatching", seul le type (ou la classe) du premier argument de la fonction générique permet d'exécuter une action spécifique. Dans l'exemple python proposé il n'y a pas de réel intérêt à utiliser le multiple dispatching mais on proposera ensuite des extensions des actions pour mieux en mesurer l'intérêt.

Versions julia simplifiées (sans "multiple dispatching")

En mode développeur, on propose une version très simplifiée de ce qui a été proposé précédemment en python et R.
cours_oop_point_class_simple
mutable struct Point
	x
	y
end

function translate(point::Point,dx,dy)
	point.x = point.x + dx
	point.y = point.y + dy
end
julia> mutable struct Point
         x
         y
       end
julia> 
julia> function translate(point::Point,dx,dy)
         point.x = point.x + dx
         point.y = point.y + dy
       end
translate (generic function with 1 method)

Notons tout de suite qu'à la différence du R, il est nullement nécessaire (comme vu pour le mode S3) de définir directement la fonction générique. En fait en Julia, définir une fonction c'est plutôt définir une méthode associée à une fonction générique comme le signale très explicitement la sortie précédente.

En mode utilisateur, on peut à présent créer un "objet" comme une instance du type Point1 et lui appliquer la méthode translate().

Instance Point (simple version)
p1 = Point(0.0,0.0);
p1

translate(p1,3,4);
p1

p2 = Point(1.2,3.0)
julia> p1 = Point(0.0,0.0);
julia> p1
Point(0.0, 0.0)
julia> 
julia> translate(p1,3,4);
julia> p1
Point(3.0, 4.0)
julia> 
julia> p2 = Point(1.2,3.0)
Point(1.2, 3.0)

Un affichage par défaut est proposé par julia, la fonction générique show() allows us to redefine the default printing

Classe Point avec méthode show
show(io::IO,point::Point)=println("Point: ($(point.x),$(point.y))")
julia> show(io::IO,point::Point)=println("Point: ($(point.x),$(point.y))")
show (generic function with 271 methods)

Méthode show exemple
p1=Point(1,2)
julia> p1=Point(1,2)
Point: (1,2)

Version julia avancée (avec "multiple dispatching")

Julia a été construit avec pour premier objectif de proposer en un seul langage (sans interfaçage avec des langages compilés C, C++ ou java) du code qui s'exécute à une vitesse comparable à celle d'un langage compilé. Donc pour atteindre cet objectif, il est connu qu'il faut le plus possible spécifier les types des variables ou champs d'une structure. Le type Point peut avoir des coordonnées de type réel, incluant en autres Int8, Int16, Int32, Int64, Int128 (les entiers avec une précision de 8, 16, 32, 64 et 128 bits), Rational (rationnel) et Float32, Float64 (réels en représentation flottante 32 ou 64 bits). Tous ces types concrets ont pour parent le type abstrait Real. On peut vérifier quelques exemples de sous-types concrets du type Real.

Sous-Types concrets de Real
Int8  <: Real, UInt64 <: Real, Bool <: Real, Float32 <: Real
julia> Int8  <: Real, UInt64 <: Real, Bool <: Real, Float32 <: Real
(true, true, true, true)

En mode développeur, julia propose un mécanisme de structures paramétriques permettant de déclarer simultanément tous les types Point{T} avec T héritant de Real. Notons au passage qu'il existe des versions de structures modifiables (mutable struct) et des versions de structures (par défaut) non modifiables (struct).

Classe Point avec multiple dispatching
mutable struct Point{T<:Real}
	x::T
	y::T
end

# Exemples d'instanciation
Point(1,2)
Point(1.0,2.0)
julia> mutable struct Point{T<:Real}
         x::T
         y::T
       end
julia> 
julia> # Exemples d'instanciation
julia> Point(1,2)
Point{Int64}(1, 2)
julia> Point(1.0,2.0)
Point{Float64}(1.0, 2.0)

Pour définir un constructeur gérant les valeurs par défaut, il suffit simplement de le définir via la méthode suivante ayant une signature vide, ici proposée dans forme compactée.

Instance de Point
Point()=Point(0.0,0.0);

# Exemple d'instanciation
Point()
julia> Point()=Point(0.0,0.0);
julia> 
julia> # Exemple d'instanciation
julia> Point()
Point{Float64}(0.0, 0.0)

En l'état, l'appel Point(1,2.0) est non valide car le type T n'est pas le même pour x et y. Pour solutionner ce problème, julia propose un mécanisme de promotion basée sur les méthodes de conversion. Les commandes suivantes permettent de convertir les arguments de la fonction promote() en un Tuple ayant le même type d'éléments à savoir le type le plus flexible permettant toutes conversions quand cela est possible (ce qui n'est pas toujours le cas comme le montre le dernière exemple).

Exemples de promotion de types
promote(1,2.0)
promote(1,2.0,true)
promote(1,2.0,true,"false")
julia> promote(1,2.0)
(1.0, 2.0)
julia> promote(1,2.0,true)
(1.0, 2.0, 1.0)
julia> promote(1,2.0,true,"false")
ERROR: promotion of types Int64, Float64, Bool and String failed to change any arguments

En utilisant les ellipsis ... qui permet de procéder un copier-coller du Tuple résultant de l'appel de la fonction promote(), on peut résoudre le problème d'intialisation avec des types différents pour x et y.

"Promotion" de types pour constructeur Point
Point(x::Real, y::Real) = Point(promote(x,y)...);

# Exemple d'instanciation
Point(1,2.0)
julia> Point(x::Real, y::Real) = Point(promote(x,y)...);
julia> 
julia> # Exemple d'instanciation
julia> Point(1,2.0)
Point{Float64}(1.0, 2.0)

De la même manière que pour les structures, julia propose des méthodes paramétriques.

Définition des méthodes
function translate(point::Point{T},dx::Real,dy::Real) where {T<:Real}
	point.x = point.x + convert(T,dx)
	point.y = point.y + convert(T,dy)
	point
end

show(io::IO,point::Point)=println("Point: ($(point.x),$(point.y))")
julia> function translate(point::Point{T},dx::Real,dy::Real) where {T<:Real}
         point.x = point.x + convert(T,dx)
         point.y = point.y + convert(T,dy)
         point
       end
translate (generic function with 1 method)
julia> 
julia> show(io::IO,point::Point)=println("Point: ($(point.x),$(point.y))")
show (generic function with 288 methods)

Sur un plan utilisateur nous pouvons à présent générer les points de manière assez souple.

Application de méthodes
p1=Point()
translate(p1,3.0,4.0);
p1
translate(p1,1,2)
translate(p1,1,2.0)
p2=Point{Float64}(1.0,2.0)
Point(1.0,2.0) #équivalent à Point{Float64}(1.0,2.0)

p3=Point(1//2,3)
julia> p1=Point()
Point: (0.0,0.0)

julia> translate(p1,3.0,4.0);
julia> p1
Point: (3.0,4.0)

julia> translate(p1,1,2)
Point: (4.0,6.0)

julia> translate(p1,1,2.0)
Point: (5.0,8.0)

julia> p2=Point{Float64}(1.0,2.0)
Point: (1.0,2.0)

julia> Point(1.0,2.0) #équivalent à Point{Float64}(1.0,2.0)
Point: (1.0,2.0)

julia> 
julia> p3=Point(1//2,3)
Point: (1//2,3//1)

Il est maintenant temps de montrer l'un des intérêts du "multiple dispatching" à savoir étendre la fonction générique translate() à des méthodes ayant des signatures différentes (::Point{T},::Point{S}) mais aussi (::Point{T},::Tuple{Real,Real})

Conversion de types pour Point
import Base.convert
convert(::Type{Point{T}}, point::Point{S}) where {T<:Real,S<:Real} = Point(convert(T,point.x),convert(T,point.y))
translate(point::Point{T},delta::Point{T}) where {T<:Real}=translate(point,delta.x,delta.y)
translate(point::Point{T},delta::Point{S}) where {T<:Real,S<:Real}=translate(point,convert(Point{T},delta));
translate(p3,p3);
p3
translate(p1,p3)
convert(Point{Float64},p3)
p1
translate(point::Point{T},delta::Tuple{Real,Real}) where {T<:Real}=translate(point,delta[1],delta[2])
translate(p1,(1.0,2//3));
p1
julia> import Base.convert
julia> convert(::Type{Point{T}}, point::Point{S}) where {T<:Real,S<:Real} = Point(convert(T,point.x),convert(T,point.y))
convert (generic function with 233 methods)
julia> translate(point::Point{T},delta::Point{T}) where {T<:Real}=translate(point,delta.x,delta.y)
translate (generic function with 2 methods)
julia> translate(point::Point{T},delta::Point{S}) where {T<:Real,S<:Real}=translate(point,convert(Point{T},delta));
julia> translate(p3,p3);
julia> p3
Point: (1//1,6//1)

julia> translate(p1,p3)
Point: (6.0,14.0)

julia> convert(Point{Float64},p3)
Point: (1.0,6.0)

julia> p1
Point: (6.0,14.0)

julia> translate(point::Point{T},delta::Tuple{Real,Real}) where {T<:Real}=translate(point,delta[1],delta[2])
translate (generic function with 4 methods)
julia> translate(p1,(1.0,2//3));
julia> p1
Point: (7.0,14.666666666666666)

Grâce au "multiple dispatching", nous pouvons utiliser la même fonction générique (ici, translate) qui pour le même type de premier argument (ici, Point{T} where {T<:Real}) peut réagir différemment grâce aux définitions des différentes méthodes selon le type du second argument. Par exemple ici, l'extension d'une Tuple{Real,Real} a été réalisée sans avoir besoin de modifier les précédentes définitions des méthodes associées à cette fonction générique. Cela n'aurait pas été le cas pour un langage limité nativement au "single dispatching" comme le python.

Pour comparer avec le "single dispatching", proposons de reécrire la méthode S3 translate.Point permettant aussi la translation d'un point. Il apparaît (du moins je l'espère) que le mécanisme de "multiple dispatching" propose des facilités d'extension de manière bien plus élégante CAR il permet de ne pas avoir à reécrire le code d'une ou plusieurs méthodes déjà existantes à chaque introduction d'une nouvelle classe (possiblement applicable à en un 2ème (ou plus) argument de ces méthodes).

Alternative au multiple dispatching en S3
translate.Point <- function(point,dx,dy) {
	if(missing(dy)) {
		if(inherits(dx,"Point")) {
			dy <- dx$x
			dx <- dx$x
		} else stop("mauvaise signature pour translate.Point")
	}
	point$x <- point$x + dx
	point$y <- point$y + dy
}
translate(p1,p2)
p1
translate(p1,1,1)
p1
translate(p1,1)
R> translate.Point <- function(point,dx,dy) {
+ 	if(missing(dy)) {
+ 		if(inherits(dx,"Point")) {
+ 			dy <- dx$x
+ 			dx <- dx$x
+ 		} else stop("mauvaise signature pour translate.Point")
+ 	}
+ 	point$x <- point$x + dx
+ 	point$y <- point$y + dy
+ }
R> translate(p1,p2)
R> p1
Point: (4.2,5.2)
R> translate(p1,1,1)
R> p1
Point: (5.2,6.2)
R> translate(p1,1)
Erreur dans translate.Point(p1, 1) : mauvaise signature pour translate.Point

Version R mode S4 (avec "multiple dispatching")

Le R dispose aussi d'un mode de programmation "multiple dispatching" que nous allons essayer d'appliquer très brièvement.
Classe S4 Point
setClass("PointS4", representation(x = "numeric", y = "numeric"))
setGeneric("translate", function(point,dx,dy) standardGeneric("translate"))
setMethod("translate",signature=c("PointS4","numeric","numeric"),function(point,dx,dy) {point@x <- point@x + dx;point@y <- point@y + dy;point})
setMethod("show",signature="PointS4",function(object)  {cat("Point: (",object@x,",",object@y,")
")})
p1 <- new("PointS4", x=1.2,y=3.0)
p1

# NB: "The usual R way" (ne pense pas que ce soit possible de modifier un "slot" à l'intérieur d'une méthode)
p1 <- translate(p1,2,3)
p1

# Essayons maintenant de rajouter une méthode permetant de translater d'un point
setMethod("translate",signature=c("PointS4","PointS4","missing"),function(point,dx,dy){point@x <- point@x + dx@x;point@y <- point@y + dx@y;point})
p2 <- new("PointS4", x=2,y=1)

p1 <- translate(p1,p2)
p1
R> setClass("PointS4", representation(x = "numeric", y = "numeric"))
R> setGeneric("translate", function(point,dx,dy) standardGeneric("translate"))
[1] "translate"
R> setMethod("translate",signature=c("PointS4","numeric","numeric"),function(point,dx,dy) {point@x <- point@x + dx;point@y <- point@y + dy;point})
R> setMethod("show",signature="PointS4",function(object)  {cat("Point: (",object@x,",",object@y,")
")})
R> p1 <- new("PointS4", x=1.2,y=3.0)
R> p1
Point: ( 1.2 , 3 )

R> # NB: "The usual R way" (ne pense pas que ce soit possible de modifier un "slot" à l'intérieur d'une méthode)
R> p1 <- translate(p1,2,3)
R> p1
Point: ( 3.2 , 6 )

R> # Essayons maintenant de rajouter une méthode permetant de translater d'un point
R> setMethod("translate",signature=c("PointS4","PointS4","missing"),function(point,dx,dy){point@x <- point@x + dx@x;point@y <- point@y + dx@y;point})
R> p2 <- new("PointS4", x=2,y=1)

R> p1 <- translate(p1,p2)
R> p1
Point: ( 5.2 , 7 )

Chacun pourra se faire sa propre idée de l'intérêt de programmer en mode S4 en R. A titre de comparaison, la manière de coder en mode "multiple dispatching" est clairement plus expressive et "légere" en julia qu'en R en mode S4.

Pour finir, soulignons que des langages orientés objets comme le C++ et java permettent de définir des méthodes encapsulées dans une structure de classe qui elles-même permettent de réagir pour un même nom de méthode différemment selon toute sa signature (i.e. toutes les classes de ses arguments).

Aller plus loin en Julia

Les graphiques avec julia

Les demos suivantes ne sont que les exemples fournis sur le site officiel de Plots.jl

image
julia> plot(Plots.fakedata(50, 5), w=3)
image
julia> plot(sin, (x->begin
                   sin(2x)
               end), 
       	0, 2π, line=4, leg=false, 
       	fill=(0, :orange)
       )
image
julia> y = rand(100)
       plot(
       	0:10:100, rand(11, 4), lab="lines", w=3, 
       	palette=:grays, fill=0, α=0.6
       )
       scatter!(
       	y, zcolor=abs.(y .- 0.5), 
       	m=(:heat, 0.8, Plots.stroke(1, :green)), 
       	ms=10 * abs.(y .- 0.5) .+ 4, lab="grad"
       )
image
julia> ys = Vector[rand(10), rand(20)]
       plot(
       	ys, color=[:black :orange], line=(:dot, 4), 
       	marker=([:hex :d], 12, 0.8, Plots.stroke(3, :gray))
       )
image
julia> plot(rand(100) / 3, reg=true, fill=(0, :green))
image
julia> scatter!(rand(100), markersize=6, c=:orange)
image
julia> histogram2d(randn(10000), randn(10000), nbins=20)
image
julia> linetypes = [:path :steppre :steppost :sticks :scatter]
       n = length(linetypes)
       x = Vector[sort(rand(20)) for i = 1:n]
       y = rand(20, n)
       plot(
       	x, y, 
       	line=(linetypes, 3), 
       	lab=map(string, linetypes), ms=15
       )
image
julia> styles = filter(
       	(s->begin
           	s in Plots.supported_styles()
           end), 
       	[:solid, :dash, :dot, :dashdot, :dashdotdot]
       )
       styles = reshape(styles, 1, length(styles))
       n = length(styles)
       y = cumsum(randn(20, n), dims=1)
       plot(y, 
       	line=(5, styles), label=map(string, styles), 
       	legendtitle="linestyle"
       )
image
julia> markers = filter((m->begin
                       m in Plots.supported_markers()
                   end), Plots._shape_keys)
       markers = reshape(markers, 1, length(markers))
       n = length(markers)
       x = (range(0, stop=10, length=n + 2))[2:end - 1]
       y = repeat(reshape(reverse(x), 1, :), n, 1)
       scatter(x, y, m=(8, :auto), 
       	lab=map(string, markers), 
       	bg=:linen, xlim=(0, 10), ylim=(0, 10)
       	)
image
julia> bar(randn(99))
image
julia> histogram(
       	randn(1000), 
       	bins=:scott, 
       	weights=repeat(1:5, outer=200)
       )
image
julia> l = @layout [a{0.1h};b{0.9h} [c;d e]]
       plot(
       	randn(100, 5), 
       	layout=l, 
       	t=[:line :histogram :scatter :steppre :bar], 
       	leg=false, ticks=nothing, border=:none
       )
image
julia> plot(
       	Plots.fakedata(100, 10), layout=4, 
       	palette=[:grays :blues :heat :lightrainbow], 
       	bg_inside=[:orange :pink :darkblue :black]
       )
image
julia> Random.seed!(111)
       plot!(Plots.fakedata(100, 10))
image
julia> n = 20
       hgt = rand(n) .+ 1
       bot = randn(n)
       openpct = rand(n)
       closepct = rand(n)
       y = OHLC[(
       	openpct[i] * hgt[i] + bot[i], 
       	bot[i] + hgt[i], bot[i], 
       	closepct[i] * hgt[i] + bot[i]) 
       	for i = 1:n]
       ohlc(y)
image
julia> y = rand(10)
       plot(y, 
       	annotations=(
       		3, y[3], 
       		text("this is #3", :left)
       	), leg=false)
       annotate!([
       	(5, y[5], text("this is #5", 16, :red, :center)), 
       	(10, y[10], text("this is #10", :right, 20, "courier"))
       	])
       scatter!(
       	range(2, stop=8, length=6), 
       	rand(6), marker=(50, 0.2, :orange), 
       	series_annotations=[
       		"series", "annotations", 
       		"map", "to", "series", 
       		text("data", :green)
       	]
       )
image
julia> verts = [
       	(-1.0, 1.0), (-1.28, 0.6), (-0.2, -1.4), 
       	(0.2, -1.4), (1.28, 0.6), (1.0, 1.0), 
       	(-1.0, 1.0), (-0.2, -0.6), (0.0, -0.2), 
       	(-0.4, 0.6), (1.28, 0.6), (0.2, -1.4), 
       	(-0.2, -1.4), (0.6, 0.2), (-0.2, 0.2), 
       	(0.0, -0.2), (0.2, 0.2), (-0.2, -0.6)
       	]
       x = 0.1:0.2:0.9
       y = 0.7 * rand(5) .+ 0.15
       plot(x, y, line=(3, :dash, :lightblue), 
       	marker=(Shape(verts), 30, RGBA(0, 0, 0, 0.2)), 
       	bg=:pink, fg=:darkblue, 
       	xlim=(0, 1), ylim=(0, 1), leg=false
       )
image
julia> x = 1:0.5:20
       y = 1:0.5:10
       f(x, y) = begin
               (3x + y ^ 2) * abs(sin(x) + cos(y))
           end
       X = repeat(reshape(x, 1, :), length(y), 1)
       Y = repeat(y, 1, length(x))
       Z = map(f, X, Y)
       p1 = contour(x, y, f, fill=true)
       p2 = contour(x, y, Z)
       plot(p1, p2)
image
julia> x = ["Nerds", "Hackers", "Scientists"]
       y = [0.4, 0.35, 0.25]
       pie(x, y, title="The Julia Community", l=0.5)
image
julia> n = 100
       ts = range(0, stop=8π, length=n)
       x = ts .* map(cos, ts)
       y = (0.1ts) .* map(sin, ts)
       z = 1:n
       plot(
       	x, y, z, zcolor=reverse(z), 
       	m=(10, 0.8, :blues, Plots.stroke(0)), 
       	leg=false, cbar=true, w=5
       )
       plot!(zeros(n), zeros(n), 1:n, w=10)
image
julia> group = rand(map((i->begin
                           "group $(i)"
                       end), 1:4), 100)
       l=@layout [a; [b c]]
       plot(
       	rand(100), layout=l, 
       	group=group, 
       	linetype=[:bar :scatter :steppre], 
       	linecolor=:match
       )
image
julia> Θ = range(0, stop=1.5π, length=100)
       r = abs.(0.1 * randn(100) + sin.(3Θ))
       plot(Θ, r, proj=:polar, m=2)
image
julia> xs = [string("x", i) for i = 1:10]
       ys = [string("y", i) for i = 1:4]
       z = float((1:4) * reshape(1:10, 1, :))
       heatmap(xs, ys, z, aspect_ratio=1)
image
julia> x = rand(10)
       p1 = plot(x, title="Default looks")
       p2 = plot(x, 
       	grid=(:y, :olivedrab, :dot, 1, 0.9), 
       	title="Modified y grid"
       	)
       p3 = plot(deepcopy(p2), title="Add x grid")
       xgrid!(p3, :on, :cadetblue, 2, :dashdot, 0.4)
       plot(p1, p2, p3, 
       	layout=(1, 3), label="", 
       	fillrange=0, fillalpha=0.3
       )
image
julia> scatter(
       fill(randn(10), 6), 
       fill(randn(10), 6), 
       framestyle=[
       	:box :semi :origin :zerolines :grid :none
       ], 
       title=[
       	":box" ":semi" ":origin" ":zerolines" ":grid" ":none"
       ],
       color=permutedims(1:6), layout=6, 
       label="", markerstrokewidth=0, 
       ticks=-2:2
       )
image
julia> t = range(0, stop=1, length=100)
       θ = (6π) .* t
       x = t .* cos.(θ)
       y = t .* sin.(θ)
       p1 = plot(x, y, line_z=t, linewidth=3, legend=false)
       p2 = scatter(x, y, 
       	marker_z=((x, y)->begin
                           x + y
                       end), 
       	color=:bluesreds, legend=false
       	)
       plot(p1, p2)

Trucs à savoir sur julia

missing

Voir documentation officielle missing values (données manquantes). Dans les premières versions de Julia, l'implémentation reposait sur un package. Maintenant, c'est intégré au coeur de Julia

Le "système" Julia

La console

La console REPL de julia est très riche puisque
  • elle permet la saisie et la modification des fonctions sur plusieurs lignes
  • elle est multimode: ] pour package, ; pour shell, ? pour aide, $ pour RCall.jl, < pour Cxx.jl et Esc pour retour au mode julia
La bonne nouvelle est que julia-VSCode intègre la console (comme le fait RStudio) pour respecter l'esprit de Julia.

Le système de paquets

Voir documentation officielle Pkg.jl

Meta-Programmation

Un atout majeur de julia est que grâce son mode compilation "Just In Time", il permet d'être utilisé comme un langage interprété (comme R et python) avec la performance d'un langage compilé (comme le C++). Une sorte de conséquence est qu'un autre atout majeur est son système de Meta-Programmation:

  1. julia (comme le R et à la différence de python) est un langage de type LISP permettant à tout code julia d'être traité comme de la donnée.
  2. julia, après une étape de parsing, permet de représenter le code à exécuter comme un arbre syntaxique. A la différence du R, l'arbre syntaxique n'est pas binaire.
  3. A la différence de R, julia n'est pas un "lazzy" langage et il propose pour remédier à cela un système de macro (commençant par @) permettant d'intercepter ses arguments puis de les traiter pour enfin exécuter le code modifié.

Tout ceci est parfaitement expliquée dans l'excellente documentation julia sur le Metaprogramming.

Mini-session sur les expressions

Expression
e = Meta.parse("1 + 2 + 3 * 4")
e2 = :(1 + 2 + 3 * 4)
e == e2
# interpolation
a = 1
e3 = :($a + 2 + 3 * 4)
eval(e3)
e3.head
e3.args
dump(e3)
Meta.show_sexpr(e3)
julia> e = Meta.parse("1 + 2 + 3 * 4")
:(1 + 2 + 3 * 4)
julia> e2 = :(1 + 2 + 3 * 4)
:(1 + 2 + 3 * 4)
julia> e == e2
true
julia> # interpolation
julia> a = 1
1
julia> e3 = :($a + 2 + 3 * 4)
:(1 + 2 + 3 * 4)
julia> eval(e3)
15
julia> e3.head
:call
julia> e3.args
4-element Vector{Any}:
  :+
 1
 2
  :(3 * 4)
julia> dump(e3)
Expr
  head: Symbol call
  args: Array{Any}((4,))
    1: Symbol +
    2: Int64 1
    3: Int64 2
    4: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol *
        2: Int64 3
        3: Int64 4
julia> Meta.show_sexpr(e3)
(:call, :+, 1, 2, (:call, :*, 3, 4))

Mini-session sur les macros

Système de Macro
macro sayhello(name)
    return :( println("Hello, ", $name) )
end

@sayhello("toto")
# Pas besoin de parenthèses
@sayhello "toto"
# affiche ce qui sera exécuté
@macroexpand @sayhello "toto"
julia> macro sayhello(name)
           return :( println("Hello, ", $name) )
       end
@sayhello (macro with 1 method)
julia> 
julia> @sayhello("toto")
Hello, toto
julia> # Pas besoin de parenthèses
julia> @sayhello "toto"
Hello, toto
julia> # affiche ce qui sera exécuté
julia> @macroexpand @sayhello "toto"
:(println("Hello, ", "toto"))

Packages pour la Statistique

Introduction au groupe JuliaStats

julia est un langage qui vient tout juste de passer en version 1.0. L'ensemble des packages tiers (faits par les contributeurs) ne sont pas encore bien stabilisés mais les contributeurs sont très actifs. Notamment soulignons le groupe JuliaStats sur github qui, comme son nom l'indique, s'organise pour proposer les outils de Statistique aux utilisateurs de julia. Un site web JuliaStats est proposé (certainement en cours de développement) pour présenter les packages principaux autour de la Statistique. Voici une liste des principaux packages qui risquent éventuellement de changer dans un avenir proche (notamment il y a un package StatsModels.jl). Le package StatsKit.jl sert de meta-package pour charger le module de la librairie Statistics) (devenue) standard (depuis la version 1.0.3 de julia) et faciliter l'installation de tous les packages suivants:

Le groupe JuliaML propose d'un site web sur le thème du Machine Learning en Julia: JuliaML

Matrice de données et Données qualitatives

Les packages DataFrames.jl (Github, Guide, Cheatsheet) et CategoricalArrays.jl (Github, Guide) proposent dans les grandes lignes les fonctionnalités nativement proposées dans R pour les matrices de données et données qualitatives car ,comme python, julia est considéré comme un langage plus généraliste que R. Comme les documentations des packages sont bien détaillées, nous ne présenterons ici que des exemples d'utilisation de base.

Données qualitatives

Pour créer directement une variable qualitative, avec des éléments possiblement manquants:
Les données qualitatives (CategoricalArray)
using CategoricalArrays # Permet le chargement de tous les packages liés à la Stat
cv = categorical(["M", missing, "M","F", "F", missing])
levels(cv)
levels!(cv, ["M", "F"]);
cv
sort(cv)
compress(cv) #permet le stockage avec un entier plus court
julia> using CategoricalArrays # Permet le chargement de tous les packages liés à la Stat
julia> cv = categorical(["M", missing, "M","F", "F", missing])
6-element CategoricalArrays.CategoricalArray{Union{Missing, String},1,UInt32}:
 "M"
 missing
 "M"
 "F"
 "F"
 missing
julia> levels(cv)
2-element Vector{String}:
 "F"
 "M"
julia> levels!(cv, ["M", "F"]);
julia> cv
6-element CategoricalArrays.CategoricalArray{Union{Missing, String},1,UInt32}:
 "M"
 missing
 "M"
 "F"
 "F"
 missing
julia> sort(cv)
6-element CategoricalArrays.CategoricalArray{Union{Missing, String},1,UInt32}:
 "M"
 "M"
 "F"
 "F"
 missing
 missing
julia> compress(cv) #permet le stockage avec un entier plus court
6-element CategoricalArrays.CategoricalArray{Union{Missing, String},1,UInt8}:
 "M"
 missing
 "M"
 "F"
 "F"
 missing

Matrice de données

Les matrices de données (DataFrames)
using DataFrames # Permet le chargement de tous les packages liés à la Stat
df = DataFrame(A = 4:-1:1, B = ["M", "F", "F", "M"], C=[4.0,2.0,1,3],D=repeat([true,false],2))
names(df)
size(df)
size(df,1)
size(df,2)
df.A
df.B[2]="M"
df
df[1:3, :]
df2=df[1:3, [:A,:C]]
typeof(df2)
df[[:A]]
df[:A]
df.A .> 2
df[df.A .> 2,:]
describe(df)
names(df, String) .=> categorical
transform!(df, names(df, String) .=> categorical, renamecols=false)
levels(df.B)
julia> using DataFrames # Permet le chargement de tous les packages liés à la Stat
julia> df = DataFrame(A = 4:-1:1, B = ["M", "F", "F", "M"], C=[4.0,2.0,1,3],D=repeat([true,false],2))
4×4 DataFrame
 Row │ A      B       C        D
     │ Int64  String  Float64  Bool
─────┼───────────────────────────────
   14  M           4.0   true
   23  F           2.0  false
   32  F           1.0   true
   41  M           3.0  false
julia> names(df)
4-element Vector{String}:
 "A"
 "B"
 "C"
 "D"
julia> size(df)
(4, 4)
julia> size(df,1)
4
julia> size(df,2)
4
julia> df.A
4-element Vector{Int64}:
 4
 3
 2
 1
julia> df.B[2]="M"
"M"
julia> df
4×4 DataFrame
 Row │ A      B       C        D
     │ Int64  String  Float64  Bool
─────┼───────────────────────────────
   14  M           4.0   true
   23  M           2.0  false
   32  F           1.0   true
   41  M           3.0  false
julia> df[1:3, :]
3×4 DataFrame
 Row │ A      B       C        D
     │ Int64  String  Float64  Bool
─────┼───────────────────────────────
   14  M           4.0   true
   23  M           2.0  false
   32  F           1.0   true
julia> df2=df[1:3, [:A,:C]]
3×2 DataFrame
 Row │ A      C
     │ Int64  Float64
─────┼────────────────
   14      4.0
   23      2.0
   32      1.0
julia> typeof(df2)
DataFrames.DataFrame
julia> df[[:A]]
ERROR: MethodError: no method matching getindex(::DataFrames.DataFrame, ::Vector{Symbol})
Closest candidates are:
  getindex(::DataFrames.DataFrame, ::AbstractVector{T}, !Matched::Colon) where T at ~/.julia/packages/DataFrames/bza1S/src/dataframe/dataframe.jl:599
  getindex(::DataFrames.DataFrame, ::AbstractVector{T}, !Matched::Union{Colon, Regex, AbstractVector, DataAPI.All, DataAPI.Between, DataAPI.Cols, InvertedIndices.InvertedIndex}) where T at ~/.julia/packages/DataFrames/bza1S/src/dataframe/dataframe.jl:573
  getindex(::DataFrames.DataFrame, ::AbstractVector, !Matched::Union{AbstractString, Signed, Symbol, Unsigned}) at ~/.julia/packages/DataFrames/bza1S/src/dataframe/dataframe.jl:524
  ...
julia> df[:A]
ERROR: ArgumentError: syntax df[column] is not supported use df[!, column] instead
julia> df.A .> 2
4-element BitVector:
 1
 1
 0
 0
julia> df[df.A .> 2,:]
2×4 DataFrame
 Row │ A      B       C        D
     │ Int64  String  Float64  Bool
─────┼───────────────────────────────
   14  M           4.0   true
   23  M           2.0  false
julia> describe(df)
4×7 DataFrame
 Row │ variable  mean    min    median  max   nmissing  eltype
     │ Symbol    Union…  Any    Union…  Any   Int64     DataType
─────┼───────────────────────────────────────────────────────────
   1 │ A         2.5     1      2.5     4            0  Int64
   2 │ B                 F              M            0  String
   3 │ C         2.5     1.0    2.5     4.0          0  Float64
   4 │ D         0.5     false  0.5     true         0  Bool
julia> names(df, String) .=> categorical
1-element Vector{Pair{String, typeof(CategoricalArrays.categorical)}}:
 "B" => CategoricalArrays.categorical
julia> transform!(df, names(df, String) .=> categorical, renamecols=false)
4×4 DataFrame
 Row │ A      B     C        D
     │ Int64  Cat…  Float64  Bool
─────┼─────────────────────────────
   14  M         4.0   true
   23  M         2.0  false
   32  F         1.0   true
   41  M         3.0  false
julia> levels(df.B)
2-element Vector{String}:
 "F"
 "M"

Machine Learning

Le package MLJ.jl (Github, Guide, Cheatsheet) est le package incourtournable pour découvrir le Machine Learning. Il propose notamment sa propre version des types de données accessibles via la fonction scitype()

Ecosystème de packages intéressants

Une excellente tour de contrôle pour se diriger vers d'excellentes ressources sur Julia est fournie sur le site officiel à Learning. Notamment, la partie Resources contient une feuille de référence A Fast Track to Julia

A propos du langage lui-même

  • PackageCompiler.jl
  • JuliaInterpreter.jl, Revise.jl and Rebugger.jl

Graphiques

  • Gadfly.jl, Makie.jl, MakieCairo.jl

Packages numériques

  • JuliaML.jl, JuMP.jl, Optim.jl

Manipulation de données

  • Query.jl, IterableTables.jl, JuliaDB.jl

Interactions avec autres langages

  • Cxx.jl, PyCall.jl, RCall.jl

Performace

  • BenchmarkTools.jl