Python va-t-il tuer R ?

Le présent

Le dernier sondage en date du site KDnuggets semble annoncer une domination future du langage Python sur le langage R dans le « petit monde »  des data sciences et du machine learning. En 2016 R pouvait encore toiser Python puisque 42% des sondés déclaraient utiliser l’implémentation open source du langage S contre 34% pour le langage au nom inspiré par les inénarrables Monty Pyhton ( 8.5% annonçant utiliser les deux) . En 2017 la tendance s’inverse : 36% pour R et 41% pour Python (et 12% pour les deux) : 

Graphique de l'évolution des parts de marché de R et Python en 2016 et 2017.

Source : KDnuggets

La tendance haussière de Python n’est pas nouvelle puisque la part d’utilisateurs de ce langage augmente de façon quasi-constante depuis 2014, en ayant par ailleurs doublé son nombre de déclarants dans ces enquêtes depuis 2014 :

Graphique de l'évolution des parts de marché de R et Python de 2014 à 2017.

Source : KDnuggets

On pourrait contester ce résultats en se basant sur le fait qu’il ne s’agit que d’enquêtes en ligne avec un bon millier de répondants. Outre le fait que ce nombre de répondants est assez classique et statistiquement suffisant pour des résultats fiables, ces résultats ne contredisent pas des enquêtes plus larges : celles d’IEEE Spectrum ou de TIOBE .

Au vu de la forte progression de Python, on peut légitimement se poser la question du titre : Python va-t-il finalement (et rapidement?) tuer R ?

La transition

Ce qui me parait particulièrement intéressant dans l’article de KDnuggets c’est le schéma suivant :

Schéma de transition entre les langages R, Python, le deux et les autres en 2017.

Source : KDnuggets

Ce dernier permet de visualiser précisément quelles sont les proportions de répondants qui sont restés fidèles à leur(s) langage(s) ou qui ont migré d’une situation à l’autre. Cela m’a rappelé un article que j’avais particulièrement apprécié  : The eigenvector of « Why we moved from language X to language Y ». Dans cet article Erik Bernhardsson avait scrappé des données à  propos des requêtes Google du type from <language 1> to <language 2>, switch to <language 2> from <language 1>. Et il en avait fait une matrice de transition pour essayer de prédire le futur… Je me suis inspiré de cet article pour appliquer la même technique aux résultats 2017 de KDnuggets.

Les informations reprises dans la figure ci-dessus peuvent être traduites en la matrice de transition suivante :

P=\begin{matrix} \begin{matrix} Pyth & Both & R & Others\end{matrix} & \\ \left( \begin{matrix} 0.91 & 0.04 & 0.05 & 0.01\\0.38 & 0.49 & 0.11 & 0.01\\0.10 & 0.15 & 0.74 & 0.01\\0.19 & 0.04 & 0.17 & 0.60 \end{matrix}\right) & \begin{array}{l} Pyth \\ Both\\ R\\ Others\end{array} \end{matrix}

Cette matrice se lit assez facilement :

  • chaque ligne représente une situation (utilisateurs de Python, utilisateurs des deux langages, …),
  • chaque colonne représente une transition possible : de la situation X vers Python pour la première, de la situation X vers les deux langages pour la deuxième,…

Par exemple la première ligne est celle des utilisateurs (ou déclarés comme tels) de Python en 2016. Ceux-ci sont restés fidèles à ce langage dans 91% des cas, ont utilisé les deux en 2017 pour 4%, ont migré vers R dans 5% des cas et vers d’autres langages pour 1% d’entre eux.  Les autres lignes peuvent se lire de façon similaire. Comme de toute situation de départ en 2016 on doit arriver à une situation existante en 2017, la somme de chacune des lignes doit faire 1. Malheureusement ce n’est pas le cas pour les lignes 1 et 2 qui dont les sommes valent respectivement 1.01 et 0.99. Ne sachant d’où vient cette très probable erreur d’arrondi (même après avoir cherché les « données brutes » sur le site de KDnuggets) j’ai appliqué une correction générale à chacune des lignes en les divisant respectivement par leurs sommes ce qui donne donc la belle matrice de transition suivante :

P=\left( \begin{matrix} 0.9009901 & 0.03960396 & 0.04950495 & 0.00990099\\0.3838384 & 0.49494949 & 0.11111111 & 0.01010101\\0.1000000 & 0.15000000 & 0.74000000 & 0.01000000\\0.1900000 & 0.04000000 & 0.17000000 & 0.60000000 \end{matrix} \right)

 

Les pourcentages utilisateurs déclarés en 2016 peuvent aussi s’écrire sous cette forme :


\pi^{(2016)}=\begin{matrix}\left(\begin{matrix} 0.34\\0.08\\0.42\\0.16\end{matrix}\right) & \begin{array}{l}Pyth \\ Both\\ R\\ Others\end{array} \end{matrix}

C’est ce que l’on appelle un vecteur d’état : il donne la probabilité de chacun des états (ici un état étant « utilisateur de… ») . La somme des nombres d’un tel vecteur devant aussi faire 1 puisque tout sondé doit se retrouver dans un des états. C’est pour respecter cette contrainte d’unité de la somme que j’ai corrigé le 8.5% en 8% en supposant que c’est une erreur puisque la somme valait 1.05 (j’aurais aussi pu tout diviser par 1.05 comme ci-dessus).

De même la situation de 2017 peut s’écrire 

\pi^{(2017)}=\begin{matrix}\left(\begin{matrix} 0.41\\0.12\\0.36\\0.11\end{matrix}\right) & \begin{array}{l}Pyth \\ Both\\ R\\ Others\end{array} \end{matrix}

L’avantage, une fois que tout est écrit sous cette forme, c’est qu’en supposant que la matrice P est une matrice stochastique décrivant un processus de Markov on peut facilement calculer ce qui se passe d’année en année (sous la supposition forte que les probabilités de transitions ne changeront pas au cours du temps), ainsi on peut écrire que :

\pi^{(2017)}= P^{\tau} \cdot \pi^{(2016)}

Et effectivement si on effectue ce calcul on retrouve, aux arrondis prés, les valeurs pour 2017:

P^{\tau} \cdot \pi^{(2016)}=\left(\begin{matrix} 0.4093163\\0.1243145\\0.3624638\\0.1039054\end{matrix}\right)

Bien entendu on peut continuer le processus et estimer ce qui se passera en 2018 (toutes choses restants égales par ailleurs comme disent les économistes) : 

\pi^{(2018)}= P^{\tau} \cdot \pi^{(2017)}=\left(\begin{matrix}0.4724950\\0.1362657\\0.3199630\\0.0712762\end{matrix}\right)

Ce sera à vérifier dans un an mais il semblerait donc que Python continuera à violemment gagner du terrain alors que la position de R continuera à s’effriter, pendant que les utilisateurs des deux progresseront timidement.

Le futur?

Évidemment, à ce stade,  on a en généralement envie (les mathématisants en tous cas)  de continuer le processus pour voir si la situation finit par se stabiliser :

\pi^{(2018)}, \pi^{(2019)}, \pi^{(2020)}\cdots, \pi^{(20xx)} \longrightarrow v

C’est évidemment assez facile à calculer en utilisant un langage comme R (!) (ou Python pour être tout à fait honnête) mais, quand s’arrêter?  « Quand les chosent se stabilisent »  pourrait-on dire platement. Mais les mathématiciens et les probabilistes se sont bien entendu déjà penchés sur ce genre de question et la réponse est assez simple : il suffit d’utiliser le concept (très puissant mais mal aimé des étudiants) de vecteurs propres et de valeurs propres. Pour rappel un vecteur v est vecteur propre de valeur propre \lambda si et seulement si :

A \cdot v= \lambda \cdot v

Or il se fait que la définition d’un état stable est assez proche car un état est dit stable pour les processus de Markov quand l’application de la matrice de transition ne change plus rien c’est-à-dire quand :

P^{\tau}\cdot v = v

en d’autres termes, un état stable est un vecteur propre de valeur propre 1. 

Si on calcule ce vecteur v pour notre match Python vs R on obtient :

v=\begin{matrix}\left(\begin{matrix} 0.67257910\\0.11156674\\0.19159885\\0.02425531\end{matrix}\right) & \begin{array}{l}Pyth \\ Both\\ R\\ Others\end{array} \end{matrix}

Ce qui signifie que dans un horizon « relativement » proche(1), Python dominerait en raflant le 2/3 du marché alors que R se contenterait d’une niche de 20% et, surprise, les utilisateurs des deux langages resteraient assez limités à  11%.

Conclusion

Évidemment, comme dans la citation prêtée à Niels Bohr (mais aussi à d’autres) :

les prévisions sont difficiles surtout lorsqu’elles concernent l’avenir.

 

Il faut donc être prudent. Mais néanmoins le jeu  des prévisions est toujours excitant et il est difficile de ne pas s’y prêter.  

Même si la répartition future entre R et Python sera vraisemblablement, au moins sensiblement, différente de celle prédite ici, la répartition de parts de chacun des langages me paraît assez plausible. En effet, outre la progression fulgurante de Python ces dernières années, ce dernier me paraît avoir des avantages décisifs (et mon coeur d’aficionados de R saigne en l’écrivant) par rapport à R. Celui-ci est un langage de programmation dédié aux statistiques et autre analyses de données, et dans ce rôle il fait un boulot remarquable, notamment soutenu par un écosystème très dynamique avec une pléthore de packages , dont certains très spécialisés. Et c’est, selon moi, une de ses forces car on peut y trouver des packages qui contiennent l’implémentation de méthodes issues directement de travaux de recherche récents. On peut donc, en tant que chercheur, y tester très rapidement de nouvelles idées. Par contre, il n’est pas  spécialement adéquat pour être utilisé « en production » pour des projets industriels avec des données massives (même si c’est possible). 

L’avantage premier de Python est que c’est un langage de programmation généraliste pour lequel on est occupé à créer des environnements/librairies dédiées aux calcul scientifique et aux data science :

Ecosystème Python pour le Machine Learning

Source: http://python.astrotech.io/machine-learning/wprowadzenie-do-machine-learning.html

Ce qui signifie que ce langage, doté de ces boîtes à outils, permet non seulement d’effectuer des analyses de données mais permet aussi de développer de véritables applications. Avantage non négligeable dans un environnement business. Et, cerise sur le gâteau, ce langage permet de développer des applications web, « d’entreprise » (sur ordinateur) et embarquées comme l’illustre ci-dessous la copie d’écran du tableau de la page d’IEEE Spectrum

 The Top Programming Languages 2018

Source : IEEE Spectrum

Même si actuellement Python n’est pas aussi riche en librairies que R et que ce dernier se débrouille nettement mieux en termes de visualisations (mais les choses changeront sans doute dans le futur), il me parait assez inéluctable que ce dernier se voie finalement retranché dans la seule niche scientifique, pour laquelle il a été a priori créé. Il vivrait ainsi un destin un peu similaire au langage Pascal, créé pour l’enseignement et qui, au vu du nombre d’étudiants ayant appris à coder avec lui se retrouvant sur le marché du travail, a vu une carrière « commerciale » s’ouvrir à lui avant que celle-ci  ne finisse par se refermer, entre autres pour des raisons techniques (comme l’absence initiale de chaînes de caractères à zéro terminal), et qu’il ne se retrouve confiné dans sa niche éducative (même si on le retrouve encore à la 17ème place du classement TIOBE).  En espérant pour R que cette niche scientifique ne soit pas elle-même réduite à peau de chagrin. La bonne nouvelle, a priori, c’est que l’on peut espérer répondre non à la question initiale.

Signé : un utilisateuR heuReux  que son langage pRéféRé se soit pas déjà condamné à une moRt rapide, même si il a déjà un peu commencé à pRogrammer en Python, on n’est jamais tRop pRudent ;).

 

(1) Un horizon de 9 à 11 ans toutes choses restant égales par ailleurs. J’ai essayé d’évaluer ce laps de temps en regardant après combien de temps |\pi^{(20xx)} - v| était en moyenne plus petit que 1% ou que le maximum de ces différences soit plus petit que 1%. Bien entendu si les proportions de migrations changent, il en sera de même de cet horizon temporel (retour au texte).

Identifier le jour de la semaine d’une date médiévale avec R

Les chirographes tournaisiens sur lesquels je travaille pour le moment donnent souvent la date de temps en indiquant un jour de la semaine. Voici un exemple :

l’an de grasce .mil.ccc.&.xiiii., lendemain dou jour S[ain]t Luc par .i. samedi ou mois d’octembre

L’acte (ici, une reconnaissance de dette) est donc passé devant les échevins en 1314, durant le mois d’octobre. Pour le dater complètement, il faut encore déterminer à quel jour correspond le samedi dont il est question, le lendemain de la Saint-Luc. Ce saint étant fêté le 18 octobre, il y a tout à parier que la date que nous cherchons correspond au 19 octobre.

En constituant ma base de données, j’ai demandé à R d’identifier les jours de la semaine qui correspondent aux dates données dans les actes. En plus d’apporter d’éventuelles informations quant à l’élaboration des chirographes (imaginons par exemple que tous les chirographes soient datés d’un même jour de la semaine, cela suggérerait que les actes de juridiction gracieuse ne passent devant les échevins qu’une fois par semaine), cette opération permet de vérifier la cohérence de la date identifiée avec celle qui est donnée dans l’acte, et d’effectuer ainsi une sorte de data quality check. J’ai pour cela utilisé l’excellent package lubridate, qui permet d’écrire par exemple pour l’acte qui nous occupe :

wday(ymd("1314-10-19"),label = TRUE,abbr = FALSE,week_start = 1)

La réponse de R pour cette date-là m’a surpris :

[1] vendredi

Après avoir vérifié que je ne m’étais pas trompé de jour pour la Saint-Luc, j’ai effectué le test pour d’autres actes, et suis toujours arrivé à la même erreur, qui donne un décalage d’un jour par rapport au jour donné dans la documentation. J’étais d’autant plus étonné que j’avais déjà utilisé ces fonctions pour identifier les jours de la semaine qui correspondaient aux actes des chirographes passés devant les échevins d’Ypres entre 1249 et 1291, et que cela fonctionnait sans problème.

Après quelques heures jours de frustration totale, j’ai fini par comprendre que le problème provenait de la différence entre le calendrier julien et le calendrier grégorien. C’est le second qui est utilisé par le package lubridate. Mais, puisqu’il n’est instauré qu’en 1582, ce n’est évidemment pas celui qui est utilisé au 14e siècle : les dates de mes chirographes tournaisiens doivent donc être comprises selon le calendrier julien. Voici ce que dit la page Wikipedia dédiée à la différence entre les deux calendriers :

Dans le calendrier julien, tous les millésimes multiples de quatre sont des années bissextiles, ce qui produit un décalage d’environ 3 jours par 400 ans par rapport à l’année astronomique. Dans le calendrier grégorien, les années séculaires ne sont normalement pas bissextiles (on parle alors d’« années communes »). Les années 1700, 1800 et 1900 furent donc bissextiles dans le calendrier julien et communes dans le calendrier grégorien. Par contre, les millésimes multiples de 400 (1600, 2000, 2400, 2800, 3200. etc.) sont bissextiles dans les deux calendriers.

Le calendrier julien « retarde » par rapport au calendrier grégorien. Ce retard était de 10 jours lors de l’instauration du calendrier grégorien en 1582. Du fait que les années séculaires ne sont normalement pas bissextiles dans le calendrier grégorien, le retard du calendrier julien s’accroit d’un jour par siècle, sauf à la fin des XVIe et XXe siècles. Le retard est actuellement de 13 jours.

La consultation d’une table de conversion entre les deux calendriers a fini de dissiper le mystère. Le problème ne s’était pas posé dans le cas des chirographes yprois du 13e siècle pour une bonne raison : le décalage est de sept jours entre 1100 et 1300, et est donc tout à fait invisible si on s’en tient aux jours de la semaine (lundi +7 = lundi !). Ce décalage passe à 8 jours à partir de 1300, raison pour laquelle j’observais un jour de différence dans les actes du 14e siècle.

Pour corriger ce problème dans R, j’ai d’abord été voir du côté du package ConvCalendar, dédié à la conversion entre différents calendriers. Hélas, celui-ci n’est plus disponible : la page CRAN du package dit

Package ‘ConvCalendar’ was removed from the CRAN repository.

J’ai donc finalement écrit une petite fonction qui fait la conversion pour les deux siècles qui m’occupent pour le moment :

library(lubridate)

Jul2Gre = function(dateJul){
    if (dateJul > dmy("28-02-1100") & dateJul <= dmy("28-02-1300")) {
        delta = 7
    } else if (dateJul > dmy("28-02-1300") & dateJul <= dmy("28-02-1400")){
        delta = 8
    } else {
        delta = NA
    }
    dateGre = dateJul + delta
    return(dateGre)
}

Le problème est ainsi résolu !

wday(Jul2Gre(dmy("19-10-1314")),label = TRUE,abbr = FALSE)
[1] samedi