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 inlineduRmais sans l'effort d'écrire le codeC++) - interfaçage natif des librairies
C(pas besoin deRcpp) - cohabitation entre structures immutables et mutables
- intégration des outils de calcul parallèle (comme un objectif initial)
- JIT (production transparente du code C précompilé avant execution: un peu comme le
- 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 deRavec unREPLet 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
S4duRmais 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,S3etS4) 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 enR) 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
Cde manière nativeRvia le packageRCall.jlqui permet notamment dans le REPL dejuliade basculer vers un REPLR(via touche$)shellaccessible via leREPL(touche;)C++,pythonvia des packages
- compaison avec
R- principales différences avec
R- rétablissement de la boucle
forperformante enjuliaet ainsi pas de programmation vectorielle requise pour performance ou d'utilisation d'outil du typeRcpp - 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
juliacompte tenu de son mode OOP)
- rétablissement de la boucle
- 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-likeavecexpression - mode statistique
Gadfly.jlun package inspiré deggplot2weave.jlun package pour faire des rapports automatiques reproducibles (commeSWeaveet son successeurknitr)
- principales différences avec
►Installation
Juliaest un langage multiplatforme reposant sur LLVM. Comme ses "potentiels" concurrents, le site officielhttps://julialang.orgpropose 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). Jupyterest 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 dejupyterest 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
Juliaa 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 langageJulia. Au lancement de la version 1.0.0, le site web a mis en avant l'écosystème de tous les domaines d'application deJulia.Juliarepose sur un système de paquetage unique (packageen 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 :- Dans la console en mode gestion de paquets : taper
]pour passer en mode gestion de package puis une fois le prompt changé enpkg>saisiradd StatsKit Plots GR. La grande toucheEffacer caractère précédentpermet de revenir au promptjulia>. - Dans la console ou un script
Julia:using Pkgpuispkg"add StatsKit Plots GR" - Dans la console ou un script
Julia:using PkgpuisPkg.add("StatsKit");Pkg.add("Plots");Pkg.add("GR")
Pkg.jl.- Dans la console en mode gestion de paquets : taper
Juliaest modulaire avec son organisation de paquets rassemblés sur le sitehttps://julialang.org/packages/. Les paquets sont généralement disponibles surGithubet, facilement et astucieusement identifiables par leur nom incluant l'extension. Il devient facile de "googler" un package.jl Juliasi 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 commepythonetR,Julian'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 duRqui 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
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
# 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
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… ─────┼────────────────────────────────────────────────────────────── 1 │ 5.1 3.5 1.4 0.2 setosa 2 │ 4.9 3.0 1.4 0.2 setosa 3 │ 4.7 3.2 1.3 0.2 setosa 4 │ 4.6 3.1 1.5 0.2 setosa 5 │ 5.0 3.6 1.4 0.2 setosa 6 │ 5.4 3.9 1.7 0.4 setosa 7 │ 4.6 3.4 1.4 0.3 setosa 8 │ 5.0 3.4 1.5 0.2 setosa 9 │ 4.4 2.9 1.4 0.2 setosa 10 │ 4.9 3.1 1.5 0.1 setosa 11 │ 5.4 3.7 1.5 0.2 setosa 12 │ 4.8 3.4 1.6 0.2 setosa 13 │ 4.8 3.0 1.4 0.1 setosa 14 │ 4.3 3.0 1.1 0.1 setosa 15 │ 5.8 4.0 1.2 0.2 setosa 16 │ 5.7 4.4 1.5 0.4 setosa 17 │ 5.4 3.9 1.3 0.4 setosa 18 │ 5.1 3.5 1.4 0.3 setosa 19 │ 5.7 3.8 1.7 0.3 setosa 20 │ 5.1 3.8 1.5 0.3 setosa 21 │ 5.4 3.4 1.7 0.2 setosa 22 │ 5.1 3.7 1.5 0.4 setosa 23 │ 4.6 3.6 1.0 0.2 setosa 24 │ 5.1 3.3 1.7 0.5 setosa 25 │ 4.8 3.4 1.9 0.2 setosa 26 │ 5.0 3.0 1.6 0.2 setosa 27 │ 5.0 3.4 1.6 0.4 setosa 28 │ 5.2 3.5 1.5 0.2 setosa 29 │ 5.2 3.4 1.4 0.2 setosa 30 │ 4.7 3.2 1.6 0.2 setosa 31 │ 4.8 3.1 1.6 0.2 setosa 32 │ 5.4 3.4 1.5 0.4 setosa 33 │ 5.2 4.1 1.5 0.1 setosa 34 │ 5.5 4.2 1.4 0.2 setosa 35 │ 4.9 3.1 1.5 0.2 setosa 36 │ 5.0 3.2 1.2 0.2 setosa 37 │ 5.5 3.5 1.3 0.2 setosa 38 │ 4.9 3.6 1.4 0.1 setosa 39 │ 4.4 3.0 1.3 0.2 setosa 40 │ 5.1 3.4 1.5 0.2 setosa 41 │ 5.0 3.5 1.3 0.3 setosa 42 │ 4.5 2.3 1.3 0.3 setosa 43 │ 4.4 3.2 1.3 0.2 setosa 44 │ 5.0 3.5 1.6 0.6 setosa 45 │ 5.1 3.8 1.9 0.4 setosa 46 │ 4.8 3.0 1.4 0.3 setosa 47 │ 5.1 3.8 1.6 0.2 setosa 48 │ 4.6 3.2 1.4 0.2 setosa 49 │ 5.3 3.7 1.5 0.2 setosa 50 │ 5.0 3.3 1.4 0.2 setosa 51 │ 7.0 3.2 4.7 1.4 versicolor 52 │ 6.4 3.2 4.5 1.5 versicolor 53 │ 6.9 3.1 4.9 1.5 versicolor 54 │ 5.5 2.3 4.0 1.3 versicolor 55 │ 6.5 2.8 4.6 1.5 versicolor 56 │ 5.7 2.8 4.5 1.3 versicolor 57 │ 6.3 3.3 4.7 1.6 versicolor 58 │ 4.9 2.4 3.3 1.0 versicolor 59 │ 6.6 2.9 4.6 1.3 versicolor 60 │ 5.2 2.7 3.9 1.4 versicolor 61 │ 5.0 2.0 3.5 1.0 versicolor 62 │ 5.9 3.0 4.2 1.5 versicolor 63 │ 6.0 2.2 4.0 1.0 versicolor 64 │ 6.1 2.9 4.7 1.4 versicolor 65 │ 5.6 2.9 3.6 1.3 versicolor 66 │ 6.7 3.1 4.4 1.4 versicolor 67 │ 5.6 3.0 4.5 1.5 versicolor 68 │ 5.8 2.7 4.1 1.0 versicolor 69 │ 6.2 2.2 4.5 1.5 versicolor 70 │ 5.6 2.5 3.9 1.1 versicolor 71 │ 5.9 3.2 4.8 1.8 versicolor 72 │ 6.1 2.8 4.0 1.3 versicolor 73 │ 6.3 2.5 4.9 1.5 versicolor 74 │ 6.1 2.8 4.7 1.2 versicolor 75 │ 6.4 2.9 4.3 1.3 versicolor 76 │ 6.6 3.0 4.4 1.4 versicolor 77 │ 6.8 2.8 4.8 1.4 versicolor 78 │ 6.7 3.0 5.0 1.7 versicolor 79 │ 6.0 2.9 4.5 1.5 versicolor 80 │ 5.7 2.6 3.5 1.0 versicolor 81 │ 5.5 2.4 3.8 1.1 versicolor 82 │ 5.5 2.4 3.7 1.0 versicolor 83 │ 5.8 2.7 3.9 1.2 versicolor 84 │ 6.0 2.7 5.1 1.6 versicolor 85 │ 5.4 3.0 4.5 1.5 versicolor 86 │ 6.0 3.4 4.5 1.6 versicolor 87 │ 6.7 3.1 4.7 1.5 versicolor 88 │ 6.3 2.3 4.4 1.3 versicolor 89 │ 5.6 3.0 4.1 1.3 versicolor 90 │ 5.5 2.5 4.0 1.3 versicolor 91 │ 5.5 2.6 4.4 1.2 versicolor 92 │ 6.1 3.0 4.6 1.4 versicolor 93 │ 5.8 2.6 4.0 1.2 versicolor 94 │ 5.0 2.3 3.3 1.0 versicolor 95 │ 5.6 2.7 4.2 1.3 versicolor 96 │ 5.7 3.0 4.2 1.2 versicolor 97 │ 5.7 2.9 4.2 1.3 versicolor 98 │ 6.2 2.9 4.3 1.3 versicolor 99 │ 5.1 2.5 3.0 1.1 versicolor 100 │ 5.7 2.8 4.1 1.3 versicolor 101 │ 6.3 3.3 6.0 2.5 virginica 102 │ 5.8 2.7 5.1 1.9 virginica 103 │ 7.1 3.0 5.9 2.1 virginica 104 │ 6.3 2.9 5.6 1.8 virginica 105 │ 6.5 3.0 5.8 2.2 virginica 106 │ 7.6 3.0 6.6 2.1 virginica 107 │ 4.9 2.5 4.5 1.7 virginica 108 │ 7.3 2.9 6.3 1.8 virginica 109 │ 6.7 2.5 5.8 1.8 virginica 110 │ 7.2 3.6 6.1 2.5 virginica 111 │ 6.5 3.2 5.1 2.0 virginica 112 │ 6.4 2.7 5.3 1.9 virginica 113 │ 6.8 3.0 5.5 2.1 virginica 114 │ 5.7 2.5 5.0 2.0 virginica 115 │ 5.8 2.8 5.1 2.4 virginica 116 │ 6.4 3.2 5.3 2.3 virginica 117 │ 6.5 3.0 5.5 1.8 virginica 118 │ 7.7 3.8 6.7 2.2 virginica 119 │ 7.7 2.6 6.9 2.3 virginica 120 │ 6.0 2.2 5.0 1.5 virginica 121 │ 6.9 3.2 5.7 2.3 virginica 122 │ 5.6 2.8 4.9 2.0 virginica 123 │ 7.7 2.8 6.7 2.0 virginica 124 │ 6.3 2.7 4.9 1.8 virginica 125 │ 6.7 3.3 5.7 2.1 virginica 126 │ 7.2 3.2 6.0 1.8 virginica 127 │ 6.2 2.8 4.8 1.8 virginica 128 │ 6.1 3.0 4.9 1.8 virginica 129 │ 6.4 2.8 5.6 2.1 virginica 130 │ 7.2 3.0 5.8 1.6 virginica 131 │ 7.4 2.8 6.1 1.9 virginica 132 │ 7.9 3.8 6.4 2.0 virginica 133 │ 6.4 2.8 5.6 2.2 virginica 134 │ 6.3 2.8 5.1 1.5 virginica 135 │ 6.1 2.6 5.6 1.4 virginica 136 │ 7.7 3.0 6.1 2.3 virginica 137 │ 6.3 3.4 5.6 2.4 virginica 138 │ 6.4 3.1 5.5 1.8 virginica 139 │ 6.0 3.0 4.8 1.8 virginica 140 │ 6.9 3.1 5.4 2.1 virginica 141 │ 6.7 3.1 5.6 2.4 virginica 142 │ 6.9 3.1 5.1 2.3 virginica 143 │ 5.8 2.7 5.1 1.9 virginica 144 │ 6.8 3.2 5.9 2.3 virginica 145 │ 6.7 3.3 5.7 2.5 virginica 146 │ 6.7 3.0 5.2 2.3 virginica 147 │ 6.3 2.5 5.0 1.9 virginica 148 │ 6.5 3.0 5.2 2.0 virginica 149 │ 6.2 3.4 5.4 2.3 virginica 150 │ 5.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)
# 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)
Les bases du langage
►Introduction aux types
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
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)
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.
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)
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
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
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 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.
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.
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)
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.
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)
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.
'a' '∀'
julia> 'a' 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase) julia> '∀' '∀': Unicode U+2200 (category Sm: Symbol, math)
b = "tot" c = "tit"
julia> b = "tot" "tot" julia> c = "tit" "tit"
julia permettant d'insérer les contenus de variables ou les résultats d'une expression dans une nouvelle chaîne de caractères
"$c $b" "1 + 2 = $(1+2)"
julia> "$c $b" "tit tot" julia> "1 + 2 = $(1+2)" "1 + 2 = 3"
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
s = "∀ x ∃ y" sizeof(s) length(s)
julia> s = "∀ x ∃ y" "∀ x ∃ y" julia> sizeof(s) 11 julia> length(s) 7
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 *
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"
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 "
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.
: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)
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}.
Array) à 1 dimension (alias Vector)strs=["apples", "bananas", "pineapples"]
julia> strs=["apples", "bananas", "pineapples"] 3-element Vector{String}: "apples" "bananas" "pineapples"
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
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.
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
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)
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.
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
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.
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
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
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
Hash dans d'autres langages, les valeurs de type Dict permmettent d'associer à une clé une unique valeur.
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"
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.
Dict par Tupled2=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
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
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 :
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)
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.
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)
Julia:returnet 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
dotvectorielle (broadcasting)
- 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
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 lequeliterateest défini sous les 2 formes initialisation et itération en un itérateur applicable à une bouclefor.Itérateur pour bouclefor# 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 domap(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
- Les instructions regroupés
begin-endet(;) - Les conditions
if-elseif-elseet la condition ternaire?chaînable - Les conditions-évaluations circuits-courts comme un possible remplacement de
switch(comme enRetpython) - Le contrôle d'erreur (
try-catchetthrow)
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++, 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
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))
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.
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)
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
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.
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.
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.
environmentclass(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")
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().
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>() où <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().
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.
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.
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")
python et R.
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().
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
showshow(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)
show exemplep1=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.
RealInt8 <: 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).
multiple dispatchingmutable 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.
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).
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.
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.
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.
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})
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).
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")
R dispose aussi d'un mode de programmation "multiple dispatching" que nous allons essayer d'appliquer très brièvement.
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
julia> plot(Plots.fakedata(50, 5), w=3)
julia> plot(sin, (x->begin sin(2x) end), 0, 2π, line=4, leg=false, fill=(0, :orange) )
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" )
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)) )
julia> plot(rand(100) / 3, reg=true, fill=(0, :green))
julia> scatter!(rand(100), markersize=6, c=:orange)
julia> histogram2d(randn(10000), randn(10000), nbins=20)
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 )
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" )
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) )
julia> bar(randn(99))
julia> histogram( randn(1000), bins=:scott, weights=repeat(1:5, outer=200) )
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 )
julia> plot( Plots.fakedata(100, 10), layout=4, palette=[:grays :blues :heat :lightrainbow], bg_inside=[:orange :pink :darkblue :black] )
julia> Random.seed!(111) plot!(Plots.fakedata(100, 10))
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)
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) ] )
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 )
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)
julia> x = ["Nerds", "Hackers", "Scientists"] y = [0.4, 0.35, 0.25] pie(x, y, title="The Julia Community", l=0.5)
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)
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 )
julia> Θ = range(0, stop=1.5π, length=100) r = abs.(0.1 * randn(100) + sin.(3Θ)) plot(Θ, r, proj=:polar, m=2)
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)
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 )
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 )
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
Julia, l'implémentation reposait sur un package. Maintenant, c'est intégré au coeur de Julia►Le "système" Julia
►La console
- 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 etEscpour retour au modejulia
RStudio) pour respecter l'esprit de Julia.►Le système de paquets
►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:
julia(comme leRet à la différence depython) est un langage de typeLISPpermettant à tout codejuliad'être traité comme de la donnée.julia, après une étape deparsing, permet de représenter le code à exécuter comme un arbre syntaxique. A la différence duR, l'arbre syntaxique n'est pas binaire.- A la différence de
R,julian'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
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
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:
StatsBase.jlDistributions.jlCategoricalArrays.jlDataFrames.jlCSV.jlDistances.jlClustering.jlGLM.jlHypothesisTests.jlKernelDensity.jlMultivariateStats.jlTimeSeries.jlBootstrap.jlLoess.jl
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
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
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
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 ─────┼─────────────────────────────── 1 │ 4 M 4.0 true 2 │ 3 F 2.0 false 3 │ 2 F 1.0 true 4 │ 1 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 ─────┼─────────────────────────────── 1 │ 4 M 4.0 true 2 │ 3 M 2.0 false 3 │ 2 F 1.0 true 4 │ 1 M 3.0 false julia> df[1:3, :] 3×4 DataFrame Row │ A B C D │ Int64 String Float64 Bool ─────┼─────────────────────────────── 1 │ 4 M 4.0 true 2 │ 3 M 2.0 false 3 │ 2 F 1.0 true julia> df2=df[1:3, [:A,:C]] 3×2 DataFrame Row │ A C │ Int64 Float64 ─────┼──────────────── 1 │ 4 4.0 2 │ 3 2.0 3 │ 2 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 ─────┼─────────────────────────────── 1 │ 4 M 4.0 true 2 │ 3 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 ─────┼───────────────────────────── 1 │ 4 M 4.0 true 2 │ 3 M 2.0 false 3 │ 2 F 1.0 true 4 │ 1 M 3.0 false julia> levels(df.B) 2-element Vector{String}: "F" "M"
►Machine Learning
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.jlJuliaInterpreter.jl,Revise.jlandRebugger.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