Site original : Sam & Max: Python, Django, Git et du cul
Mettre son projet en production, c’est la galère. Tellement que mille méthodes ont vu le jour pour automatiser tout ça. Chef, salt, fabric, des script bash, virtualenv, git hooks, etc.
Après, il y a ceux qui utilisent des VM, qui elles, ont leur propres outils d’automatisation type Vagrant.
Et comme ça ne suffit pas, des services ce sont mis en place pour faciliter la mise en prod dans le cloud comme Heroku, Gondor, dotCloud…
Malgré ça, Max fait encore beaucoup de trucs à la main parce que “ça marche jamais comme prévu”. Pas très scalable.
Dernièrement, grâce à notre cher Cortex, j’ai découvert un projet écrit en Go nommé Docker, qui propose encore une autre approche du problème.
J’ai été intrigué après avoir visionné une conf sur le sujet car le principe est très cool, mais aussi parce que j’ai bossé à une époque avec un des mecs de chez docker. Et ce gars est un monstre. Mais vraiment. Une brute. Le genre qui n’a pas besoin de souris ni de gestionnaire de fenêtre, mais qui peut vous régler votre problème en tapant d’une main tout en vous expliquant ce qu’il fait dans votre domaine d’expertise alors que ce n’est pas le sien. Je l’ai vu faire. C’est énervant.
Pour le moment, je dois dire que Docker est vraiment sympa. Petit tour du propriétaire.
Pour faire simple, docker, c’est de la virtualisation légère.
Ça marche comme cela : on prend une image d’un Linux de base qu’on fait tourner dans docker, on lui installe de quoi faire tourner un process – par exemple redis – et on obtient une nouvelle image qui contient juste la différence avec l’image précédente. On fait ça avec plein d’autres process, et quand on a besoin de l’un d’entre eux, on lance l’image qui contient l’install de ce celui-ci.
Ce sont des images très légères, on peut en lancer 100 sur une même machine si on le souhaite. Mais elles sont parfaitement isolées : elles ont leur propre système de fichier, leurs propres arbres de processus, utilisateurs, permissions, ports… Donc si par exemple je fais un nginx compilé avec des extensions exotiques et une config zarb, je peux en faire une image puis l’utiliser sur n’importe quel serveur qui contient Docker.
Le but, c’est de créer plein de petits conteneurs légers et isolés de votre machine. Et au lieu de configurer chaque serveur, on envoie juste les conteneurs dont on a besoin, et on est certain qu’ils marchent partout pareil, peu importe la config à l’arrivée. On virtualise des services, qu’on combine, et non une machine complète.
Quand je vous dis que c’est léger, c’est VRAIMENT léger. A peine plus gourmand que le process sans la VM. En fait, un conteneur docker démarre presque instantanément, il n’y a pas de “boot” comme pour une vraie VM. Mais on en a les avantages car votre redis dockerisé marchera pareil sur une Fedora et une Ubuntu.
Docker ne marche que sur Linux pour le moment, et encore, sur un Linux pas trop vieux car il utilise une technologie récente, dont vous avez probablement peu entendu parlé : LXC.
Détail amusant : Docker est pas mal utilisé dans la communauté des devs sous Mac, qui du coup ont une VM Ubuntu dans laquelle ils font tourner Docker pour travailler. N’est-ce pas merveilleux ?
Je ne vais pas vous laisser comme ça et vous demander de vous la mettre derrière l’oreille, donc exemple d’utilisation sous Bubuntoune.
Je suis en 14.04, mais je pense que ça marche pareil depuis la 12.04.
Installation :
$ sudo apt-get install docker.io
Cela va mettre la commande docker.io
à votre disposition, mais sous d’autres systèmes, elle s’appelle juste docker
. Perso je fais un
alias docker="sudo docker.io"
puisque de toute façon il faut toujours lancer cette commande avec les droits admin.
Ensuite, on va télécharger une image de base sur laquelle baser toutes nos images :
$ docker pull ubuntu
Docker.io, ce n’est pas juste un software, c’est aussi un service qui héberge les images. Vous pouvez bien entendu créer vos propres images de base, et héberger votre dépôt personnel d’images, mais on ne va pas se faire chier à faire ça ici. Prévoyez quelques gigas de place, Docker ne prend pas beaucoup de ressources CPU/RAM, mais par contre ça bouffe en espace disque.
Donc là, je demande la récupération des images “ubuntu”, qui vont nous servir d’images de base. Elles sont toutes faites et prêtes à l’emploie, que demande le peuple ?
Ça va downloader pendant pas mal de temps, puis vous aurez plusieurs images à votre disposition, que l’on peut lister ainsi :
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) ubuntu 12.10 b750fe79269d 5 months ago 24.65 kB (virtual 180.1 MB) ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB) ubuntu quantal b750fe79269d 5 months ago 24.65 kB (virtual 180.1 MB)
Vous faites votre marché : quelle image de base voulez-vous utiliser ?
La 12.04 est une LTS qui est déjà bien field testée, donc je vais prendre celle là.
Ensuite, pour lancer une commande avec, il faut faire docker run id_image commande
. Ceci va lancer l’image, lancer la commande, et dès que la commande se termine, arrêter l’image :
$ docker run 74fe38d11401 echo 'YEAHHHHHHHHHHHHHH' WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4] YEAHHHHHHHHHHHHHH
Mesurons le temps pris en tout pour faire cela :
$ time -f "%e seconds" sudo docker.io run 74fe38d11401 echo 'YEAHHHHHHHHHHHHHH' WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4] YEAHHHHHHHHHHHHHH 0.45 seconds
Comme vous le voyez, démarrer un conteneur, c’est vraiment rapide. Mais faire un
echo
, ça ne sert pas à grand chose :)
Faisons quelque chose de plus utile : lançons un terminal bash et demandons un accès à stdin/stdout (-i
) ainsi qu’un tty (-t
) :
$ docker run -i -t 74fe38d11401 bash WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4] root@8195e92a5e62:/#
Et hop, comme le process ne se termine pas tant qu’on est connecté au tty, on a le conteneur qui continue de tourner, mais on a un terminal dessus, nous permettant d’entrer n’importe quelle commande.
Si on installait 0bin ?
D’abord, on a besoin de pip dans le conteneur:
# apt-get install python-pip
Notez qu’on est toujours root dans son conteneur. Après tout, c’est virtuel et isolé, donc pas de raison de se faire chier.
Ensuite, on tape dans pypi :
# pip install zerobin
Pas besoin de virtualenv, on est déjà dans un environnement isolé.
Faisons un petit fichier de config. On va avoir besoin de vim :
# apt-get install vim # cd /home # vi settings.py
On met des settings perso pour zerobin, histoire de montrer le principe de faire un conteneur avec un setup sur mesure (on peut, bien entendu, faire 1000 fois plus compliqué, c’est un exemple bande de bananes) :
# on le fait ecouter sur l'exterieur HOST = "0.0.0.0" # on change le menu de la page d'accueil MENU = ( ('Home', '/'), ('Contact', 'mailto:mysupermail@awesomesite.com') )
Et on lance notre petit zerobin :
# zerobin --settings-file=settings.py
Et voilà, on a un zerobin qui tourne dans notre conteneur isolé.
On va sauvegarder tout ça. Sur notre machine hôte (donc pas dans le conteneur), on va sauvegarder notre nouvelle image. D’abord, on va trouver l’ID du conteneur qui tourne :
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8195e92a5e62 ubuntu:12.04 bash 37 minutes ago Up 37 minutes boring_mccarthy
Ensuite on commit ce conteneur pour obtenir une nouvele image. Pour faire simple, on va l’appeller “zerobin” :
$ docker commit 8195e92a5e62 zerobin
On peut alors tranquillement arrêter notre conteneur :
$ docker stop 8195e92a5e62
Maintenant on peut pusher notre image tout neuve sur un repo distant avec docker push
pour pouvoir la puller plus tard. Il faut ouvrir un compte sur leur service ou créer son propre depot, je vous laisse trouver comment faire. Plus simplement, on peut juste dumper l’image avec docker save id_image > nom_image.tar
, la copier sur une clé USB, et la recharger avec docker load < nom_image.tar
.
Dans tous les cas, une fois sur le serveur, vous pouvez lancer votre image "zerobin", ici encore une fois avec la commande bash:
# docker run -t -i -p 7777:8000 zerobin bash
Cette fois, on rajoute une option de plus : -p 7777:8000
dit à docker de relier le port 7777
de ma machine au port 8000
(port de 0bin par défaut) de mon conteneur. Ainsi, si une app va sur le port 7777
de ma machine, elle va en fait parler au port 8000
du conteneur sans s'en rendre compte.
Du coup, je peux lancer mon petit zerobin depuis mon contenur :
# cd /home # zerobin --settings-file=settings.py
Et voilà ! J'ai un zerobin qui marche, qui est préconfiguré, isolé et portable. Si je change de serveur, ou même d'OS, je peux juste reprendre le même conteneur et le relancer tel quel. Et je peux faire ça avec plein de process et les faire communiquer entre eux.
Docker est un outil très riche, et vous vous doutez bien que je ne vous ai montré que le début.
Par exemple, vous voulez sans doute ne pas avoir à lancer bash, puis un cd
, puis zerobin. Ça peut s'automatiser avec un dockerfile. Vous voulez aussi peut être lancer le conteneur en arrière plan, ça se fait avec l'option -d
. Ou peut être voulez-vous un dossier partagé entre tous les conteneurs ? C'est possible avec l'option volume.
Parmi les usages vraiment intéressants de dockers : packager les services très custos comme les nginx ou ffmpeg compilés avec des options cryptiques, deployer son app django avec toutes les libs Python et les settings qui vont bien, remplacer une app à chaud en redirigeant juste le load balancer sur le nouveau conteneur, ou encore fournir un env de test à un client. Perso, rien que pour tester des nouveaux logiciels sans pourir ma machine, je trouve ça pratique : on a bien moins peur de faire un make install
.
Je vous laisse prendre en main le nouveau joujou, j'ai des serveurs à planter.
Sans répit, des hordes d’OP ont demandé sur Stackoverflow comment compiler un programme Python. La plupart du temps, pour des raisons d’obfuscation ou pour faire un joli .exe. Et inlassablement, les devs bienveillants leur répondaient que Python était un langage interprété, et donc non compilable.
Commença alors la lente fuite des impatients vers Go qui permettait de le faire si facilement.
Et puis arriva Cython, qui promettait de permettre de transformer du Python en C, afin de l’embarquer dans un autre code C, ou permettre des appels de C vers Python et inversement plus facile.
Cela visait les perfs, l’intégration, faire des dll/so en Python, et du coup, dans l’excitation, tout le monde a loupé un petit détail minuscule.
Les mecs avaient implémenté un compilateur Python => C complet.
En fait, Cython permet – et c’est la que c’est fun car c’est un effet de bord presque involontaire – de créer un programme compilé autonome en Python. Et avec la toolchain classique en plus.
Ça peut gérer les dépendances, et ça marche même avec des trucs compilés balèzes du genre PyQT.
Je sens le chapiteau se dresser sous la braguette, alors voici la démo…
Pour montrer qu’on ne fait pas que du “Hello World”, je vais utiliser des dépendances assez complexes : lxml (qui contient une extension C compilée), path.py, requests et docopt :
""" Download a file and save it to a location. Usage: downloader.py <target> [<location>] """ import requests import docopt from lxml.html import parse from path import path args = docopt.docopt(__doc__) p = path(args['<location>']).realpath() if p.isdir(): p = p / path(args['<target>']).namebase p.write_bytes(requests.get(args['<target>']).content) title = parse(p).getroot().find('head').find('title').text print('Downloaded "%s"' % title)
Je ne m’encombre pas de gestion d’erreurs, juste suffisamment d’appels pour faire travailler les dépendances.
Qui dit compilation, dit environnement. Pour ma part, je suis sous Ubuntu 14.04 et je vais me compiler un petit programme en Python 3, avec lesquels il me faut installer Cython et pip pour Python 3, de quoi compiler du C, et les dépendances de notre script :
$ sudo apt-get install gcc cython3 python-pip3 python3-lxml $ pip3 install requests docopt path.py
Je n’ai même pas eu besoin d’installer les headers de lxml. Vive le Dieu des geeks.
L’utilisation de Cython dans notre cas est assez simple : on lui dit de transformer notre module en module C. --embed
lui dit d’inclure l’interpréteur Python dedans.
$ cython3 downloader.py -o downloader.c --embed
On obtient un fichier C bien copieux :
$ head downloader.c /* Generated by Cython 0.20.1post0 (Debian 0.20.1+git90-g0e6e38e-1ubuntu2) on Sun May 11 22:53:18 2014 */ #define PY_SSIZE_T_CLEAN #ifndef CYTHON_USE_PYLONG_INTERNALS #ifdef PYLONG_BITS_IN_DIGIT #define CYTHON_USE_PYLONG_INTERNALS 0 #else #include "pyconfig.h" #ifdef PYLONG_BITS_IN_DIGIT #define CYTHON_USE_PYLONG_INTERNALS 1
Il ne reste plus qu’à compiler ce dernier :
$ gcc -Os -I /usr/include/python3.4m downloader.c -o download -lpython3.4m -lpthread -lm -lutil -ldl
J’ai mis -I /usr/include/python3.4m
et -lpython3.4m
car les headers de Python sont là dedans sur ma machine. Le reste, ce sont des options que j’ai copier / coller sur le Web car GCC a plus de flags qu’une ambassade américaine et que j’ai des choses plus importantes à retenir, comme par exemple la recette du guacamole.
On obtient un exécutable tout à fait exécutablatoire :
$ chmod u+x downloader $ ./downloader # docopt marche :) Usage: downloader.py <target> [<location>]
Et ça télécharge comme prévu :
$ ./downloader http://sametmax.com index.html Downloaded "Sam & Max: Python, Django, Git et du cul | Deux développeurs en vadrouille qui se sortent les doigts du code"
Le script Python original fait 472 octets, le binaire obtenu 28,7 ko. Mais ce n’est pas standalone, puisque je n’ai pas demandé la compilation des dépendances (je viens de le tester sur une autre Ubuntu, il pleure que requests n’est pas installé). Je vous laisse vous faire chier à trouver comment faire pour répondre à votre cas de figure exact, mais ça implique d’utiliser cython_freeze.
Après tout, cet article est intitulé “Peut on compiler un programme Python ?” et non “Tuto pour rendre un script Python stand alone”. Je ne suis pas fou.
Apparemment ça marche sous Windows et Mac, même si je n’ai pas de quoi tester sous la main (bon, si j’ai une partition Windows, mais faut rebooter, tout réinstaller, merde quoi…).
Donc si vous voulez faire une application en Python et la rendre stand alone, l’obfusquer, pondre un exe pour rendre le téléchargement facile, rassurer les gens ou simplement ignorer les mises à jours des libs de l’OS, Cython est une bonne piste.
Petit bonus, votre programme sera plus rapide, car il saute l’étape d’interprétation. Bien entendu, vous récoltez les galères qui viennent avec la compilation, à savoir les différentes architectures, le linking vers les libs qui peuvent changer de place ou de versions, etc.
Ceci est un post invité de joshuafr posté sous licence creative common 3.0 unported.
Bonjour à tous, jeunes tailleurs de bambou, suite à un article d’introduction à numpy par le grand maître Sam Les bases de Numpy, je m’en vais vous présenter une lib qui roxx du poney dans le calcul numérique : Pandas.
Pour faire simple, Pandas apporte à Python la possibilité de manipuler de grands volumes de données structurées de manière simple et intuitive, chose qui faisait défaut jusqu’ici. Il y a bien eu quelques tentatives comme larry, mais rien n’avait jamais pu égaler les fonctionnalités du langage R. Aujourd’hui Pandas y arrive en fournissant notamment le célèbre type dataframe de R, avec en prime tout un tas d’outils pour agréger, combiner, transformer des données, et tout ça sans se casser le cul. Que du bonheur!
Donc pour commencer, on installe le bousin par un simple : pip install pandas
qui va si vous ne l’avez pas déjà fait, aussi télécharger/compiler/installer tout un tas de librairies dont numpy. Je vous conseille aussi d’utiliser ipython afin d’avoir une meilleure interaction avec les libs, notamment avec matplotlib en utilisant le switch ipython --pylab
afin d’avoir directement les graphiques en mode interactif, ainsi que toute la bibliothèque numpy directement importée (en interne, ipython fera un import numpy as np
).
On appelle la bête d’un simple:
In [1]: import pandas as pd
Oui je sais, la grande classe…
Le type de base en Pandas et la Series. On peut le voir comme un tableau de données à une dimension:
In [2]: pd.Series(np.arange(1,5)) Out[2]: 0 1 1 2 2 3 3 4 dtype: int64
La colonne de gauche représente l’index de la Series, normalement unique pour chaque entrée. La colonne de droite correspond à nos valeurs sur lesquelles nous voulons travailler.
L’index n’est pas forcément une suite d’entiers, et la Series peut être nommée:
In [3]: s=pd.Series([1,2,3.14,1e6], index=list('abcd'), name='ma_series') In [4]: s Out[4]: a 1.00 b 2.00 c 3.14 d 1000000.00 Name: ma_series, dtype: float64
A noter qu’un type-casting est systématiquement appliqué afin d’avoir un tableau de type uniforme (ici le data-type est du float64) qui peut être modifié (dans une certaine mesure) via Series.astype
.
Le slicing c’est comme du fisting avec une bonne dose de vaseline, ça glisse tout seul:
In [5]: s['b':'d'] Out[5]: b 2.00 c 3.14 d 1000000.00 Name: ma_series, dtype: float64
Et oui, la sélection par indexation se fait sur… l’index de la Series. Ainsi s['a'] renverra la ligne dont l’index est ‘a’, mais Pandas est assez intelligent pour reconnaître si on lui demande de nous renvoyer des valeurs suivant l’ordonnancement du tableau de valeurs (comme numpy). Ainsi s[0] renverra la première valeur du tableau, qui ici est égale à s['a'].
Là où ça peut poser problème c’est quand notre index est une suite d’entiers, comme par exemple avec x=pd.Series(np.arange(1,5), index=np.arange(1,5))
. Si vous demandez x[1]
, Pandas ne retrouve pas ses petits et vous retournera une zolie KeyError
. Pour ces cas ambigus, il existe l’indexation stricte sur le nom de index de la Series via x.loc[nom_d'index]
, et l’indexation stricte sur le numéro d’ordre dans le tableau via x.iloc[numéro_d'ordre]
. Essayez x.loc[0]
et x.iloc[0]
pour vous rendre compte de la différence.
Comme pour les préliminaires où il est bon de tâter un peu de tout avec de pénétrer dans le vif du sujet, laissons pour le moment l’indexation sur laquelle nous reviendrons plus tard, pour regarder d’un peu plus près comment faire joujou avec nos valeurs.
Un peu à la manière des arrays de numpy, on peut appliquer des fonctions mathématiques directement sur la Serie, ou passer par des fonctions raccourcis:
In [6]: s.sum() Out[6]: 1000006.14
Ce qui revient au même que de faire np.sum(s)
(rappelez vous, ipython avec –pylab a importé numpy dans la variable np).
La fonction describe
est bien utile pour avoir un aperçu rapide de ce à quoi on a affaire:
In [7]: s.describe() Out[7]: count 4.000000 mean 250001.535000 std 499998.976667 min 1.000000 25% 1.750000 50% 2.570000 75% 250002.355000 max 1000000.000000 Name: ma_series, dtype: float64
ce qui donne le nombre de données, la moyenne globale, la déviation standard, le minimum, les quartiles et le maximum de la Serie.
Le truc à retenir est que c’est l’index qui est primordial dans un grand nombre d’opérations. Ainsi si l’on veut additionner 2 Series ensemble, il faut que leurs index soient alignés :
In [8]: s2=pd.Series(np.random.rand(4), index=list('cdef'), name='autre_serie') In [9]: s+s2 Out[9]: a NaN b NaN c 4.021591 d 1000000.401511 e NaN f NaN dtype: float64
Ici, seuls les index ‘c’ et ‘d’ étaient présents dans les 2 Series, Pandas effectuant avant l’opération d’addition une union basée sur l’index. Les autres entrées sont marquées en NaN
, soit Not a Number. Une possibilité pour contrer ce phénomène et de dire à Pandas de remplacer les résultats manquants lors de l’union par 0:
In [10]: s.add(s2, fill_value=0) Out[10]: a 1.000000 b 2.000000 c 4.021591 d 1000000.401511 e 0.563508 f 0.655915 Name: ma_series, dtype: float64
Mais si ce sont uniquement les valeurs qui nous intéressent, et non les indexations, il est possible de les supprimer:
In [11]: s.reset_index(drop=True)+s2.reset_index(drop=True) Out[11]: 0 1.881591 1 2.401511 2 3.703508 3 1000000.655915 dtype: float64
Oh joie, oh bonheur, je peux faire ce que je veux avec mes cheveux, enfin mes données…
La DataFrame est l’extension en 2 dimensions des Series. Elle peut être vue comme un empilement de Series dont les index sont partagés (et donc intrinsèquement alignés), ou comme dans un tableur où les index sont les numéros de lignes et les noms des Series les noms des colonnes. Je ne vais pas décrire toutes les manières de créer une DataFrame, sachez juste qu’on peut les obtenir à partir de dictionnaires, de liste de liste ou de liste de Series, d’arrays ou de records numpy, de fichier excel ou csv et même depuis des bases de données, de fichier JSON ou HTML, et depuis le presse-papiers.
In [14]: genre=[['femme','homme'][x] for x in np.random.random_integers(0,1,100)] In [15]: lateral=[['droite','gauche'][x] for x in np.random.random_integers(0,1,100)] In [16]: age=np.random.random_integers(1,100,100) In [17]: df=pd.DataFrame({'Genre':genre, 'Lateral':lateral, 'Age':age}) In [18]: df Out[18]: Age Genre Lateral 0 69 femme droite 1 46 homme droite 2 89 homme droite 3 14 homme droite 4 74 homme droite 5 5 femme gauche 6 66 femme droite 7 73 homme gauche 8 99 homme gauche 9 17 homme gauche ... ... ... [100 rows x 3 columns]
L’affichage par défaut depuis la version 0.13 est en mode ‘truncate’ où la fin de la DataFrame est tronquée suivant la hauteur de votre terminal, mais ça peut se changer via les divers paramètres à regarder sous pd.options.display
.
Là donc nous avons une DataFrame de 3 colonnes (plus un index), chaque colonne étant en réalité une Serie :
In [20]: type(df['Age']) Out[20]: pandas.core.series.Series
La sélection peut se faire de plusieurs manières, à chacun de choisir sa préférée (moi c’est Dafnée avec ses gros nénés). Ainsi pour avoir les 3 premières lignes des âges
In [21]: df['Age'][0:3] Out[21]: 0 69 1 46 2 89 Name: Age, dtype: int64 In [22]: df[0:3]['Age'] Out[22]: 0 69 1 46 2 89 Name: Age, dtype: int64 In [23]: df.Age[0:3] Out[23]: 0 69 1 46 2 89 Name: Age, dtype: int64 In [24]: df.loc[0:3, 'Age'] Out[24]: 0 69 1 46 2 89 3 14 Name: Age, dtype: int64
et oui, les noms de colonnes peuvent aussi être utilisés comme des attributs de la DataFrame. Pratique (qu’on n’attend pas).
L’une des forces de Pandas est de nous proposer tout un tas de solutions pour répondre à des questions existentielles tel que “quel est l’âge moyen par genre et par latéralité?”. Comme en SQL où la réponse sortirait du fondement d’une clause GROUP BY et d’une fonction d’agrégation, il en va de même ici :
In [25]: df.groupby(['Genre','Lateral']).aggregate(np.mean) Out[25]: Age Genre Lateral femme droite 45.476190 gauche 49.208333 homme droite 41.571429 gauche 55.823529 [4 rows x 1 columns]
OMG! c’est quoi c’t'index de malade? Un MultiIndex jeune padawan, qui te permettra d’organiser tes données par catégorie/niveau, et d’y accèder par le paramètre level
dans pas mal de fonctions, mais ça je te laisse le découvrir par toi-même. Je ne vais pas non plus m’étendre plus sur toutes les possibilités offertes par les DataFrame, il y a tellement à dire qu’il faudrait plusieurs articles pour en faire le tour. Juste conclusionner sur la facilité d’intégration Pandas/matplotlib en vous disant que les Series et DataFrame ont une fonction plot
permettant directement de visualiser les données, et ça, c’est juste jouissif.
Je vous avez dit qu’on reviendrait sur les indexes, et là c’est pour rentrer dans le lourd (mais non pas toi Carlos). Pandas donc supporte l’indexation sur les dates, en reprenant et en élargissant les possibilités offertes par feu le module scikits.timeseries.
Prenons l’exemple de données (complètement bidons) fournies par un capteur à intervalle régulier sur un pas de temps horaire:
In [26]: dtindex=pd.date_range(start='2014-04-28 00:00', periods=96, freq='H') In [27]: data=np.random.random_sample(96)*50 In [28]: df=pd.DataFrame(data, index=dtindex, columns=['mesure']) In [29]: df.head() Out[29]: mesure 2014-04-28 00:00:00 49.253929 2014-04-28 01:00:00 1.910280 2014-04-28 02:00:00 7.534761 2014-04-28 03:00:00 39.416415 2014-04-28 04:00:00 44.213409 [5 rows x 1 columns] In [30]: df.tail() Out[30]: mesure 2014-05-01 19:00:00 25.291453 2014-05-01 20:00:00 26.520291 2014-05-01 21:00:00 33.459766 2014-05-01 22:00:00 44.521813 2014-05-01 23:00:00 28.486003 [5 rows x 1 columns]
dtindex
est un DatetimeIndex initialisé au 28 avril 2014 à 0 heure comportant 96 périodes de fréquence horaire, soit 4 jours. La fonction date_range
peut aussi prendre en arguments des objets datetime purs au lieu de chaine de caractère (manquerait plus que ça…), et le nombre de périodes peut être remplacé par une date de fin.
Si l’on veut calculer, disons le maximum (horaire) par jour, rien de plus simple, il suffit de “resampler” en données journalières (‘D’ pour Day) et de dire comment aggréger le tout:
In [31]: df.resample('D', how=np.max) Out[31]: mesure 2014-04-28 26.298282 2014-04-29 28.385418 2014-04-30 26.723353 2014-05-01 24.106092 [4 rows x 1 columns]
Mais on peut aussi convertir en données quart-horaire (upsampling) en remplissant les données manquantes par celles de l’heure fixe:
In [32]: df[:3].resample('15min', fill_method='ffill') Out[32]: mesure 2014-04-28 00:00:00 49.253929 2014-04-28 00:15:00 49.253929 2014-04-28 00:30:00 49.253929 2014-04-28 00:45:00 49.253929 2014-04-28 01:00:00 1.910280 2014-04-28 01:15:00 1.910280 2014-04-28 01:30:00 1.910280 2014-04-28 01:45:00 1.910280 2014-04-28 02:00:00 7.534761 [9 rows x 1 columns]
Cependant, Pandas propose aussi d’autres possibilités non dépendantes des DatetimeIndex mais qu’il est bon de connaître, notamment celle pour remplacer les données manquantes avec fillna
ou celle pour interpoler entre les données valides avec interpolate
In [52]: df[:3].resample('15min') Out[52]: mesure 2014-04-28 00:00:00 49.253929 2014-04-28 00:15:00 NaN 2014-04-28 00:30:00 NaN 2014-04-28 00:45:00 NaN 2014-04-28 01:00:00 1.910280 2014-04-28 01:15:00 NaN 2014-04-28 01:30:00 NaN 2014-04-28 01:45:00 NaN 2014-04-28 02:00:00 7.534761 [9 rows x 1 columns] In [53]: df[:3].resample('15min').fillna(df.mean()) Out[53]: mesure 2014-04-28 00:00:00 49.253929 2014-04-28 00:15:00 26.378286 2014-04-28 00:30:00 26.378286 2014-04-28 00:45:00 26.378286 2014-04-28 01:00:00 1.910280 2014-04-28 01:15:00 26.378286 2014-04-28 01:30:00 26.378286 2014-04-28 01:45:00 26.378286 2014-04-28 02:00:00 7.534761 [9 rows x 1 columns] In [54]: df[:3].resample('15min').interpolate() Out[54]: mesure 2014-04-28 00:00:00 49.253929 2014-04-28 00:15:00 37.418016 2014-04-28 00:30:00 25.582104 2014-04-28 00:45:00 13.746192 2014-04-28 01:00:00 1.910280 2014-04-28 01:15:00 3.316400 2014-04-28 01:30:00 4.722520 2014-04-28 01:45:00 6.128641 2014-04-28 02:00:00 7.534761 [9 rows x 1 columns]
Voilà, j’espère que vous aurez plaisir à travailler avec cette librairie, il manquait vraiment un outil de cette trempe en Python pour l’analyse de données et je pense qu’on n’a plus trop grand chose à envier maintenant par rapport à des langages spécilisés. Je n’ai pas parlé de Panel
qui est le passage à la troisième dimension, ni des possibilités d’export, notamment la df.to_html
que je vous laisse le soin de découvrir.
A plus, et amusez vous bien avec votre bambou.
\o/
Je suis en train de rendre visite à Max. Il va bien, il a toutes ses dents, la truffe humide et le poil soyeux.
Par contre :
⟩ ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=36 time=384 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=36 time=1134 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=36 time=1351 ms 64 bytes from 8.8.8.8: icmp_seq=5 ttl=36 time=873 ms 64 bytes from 8.8.8.8: icmp_seq=6 ttl=36 time=1258 ms 64 bytes from 8.8.8.8: icmp_seq=7 ttl=36 time=1299 ms 64 bytes from 8.8.8.8: icmp_seq=8 ttl=36 time=1431 ms 64 bytes from 8.8.8.8: icmp_seq=9 ttl=36 time=1461 ms 64 bytes from 8.8.8.8: icmp_seq=10 ttl=36 time=1474 ms 64 bytes from 8.8.8.8: icmp_seq=11 ttl=36 time=1178 ms 64 bytes from 8.8.8.8: icmp_seq=12 ttl=36 time=1932 ms 64 bytes from 8.8.8.8: icmp_seq=13 ttl=36 time=2023 ms 64 bytes from 8.8.8.8: icmp_seq=14 ttl=36 time=1818 ms 64 bytes from 8.8.8.8: icmp_seq=15 ttl=36 time=1546 ms 64 bytes from 8.8.8.8: icmp_seq=16 ttl=36 time=1188 ms 64 bytes from 8.8.8.8: icmp_seq=17 ttl=36 time=1110 ms 64 bytes from 8.8.8.8: icmp_seq=18 ttl=36 time=998 ms ^C --- 8.8.8.8 ping statistics ---
Et je vous raconte pas comme c’est chiant avec WordPress.
Article ultra débutant, mais hey, encore un truc très peu expliqué sur Internet pour l’excuse bidon que “c’est évident”.
Il a été conçu dans le but de nuire en utilisant la machine qu’il infecte. C’est donc un logiciel en plus sur votre machine, et qui pose des problèmes. Attention, un virus ne va pas forcément ralentir votre ordinateur ou le faire planter. Les virus modernes vont justement, en général, essayer de garder votre ordinateur en bon état (parfois en désinstallant d’autres virus !), afin d’avoir confortablement l’usage de votre machine.
Le virus entre dans la catégorie des programmes dits “malwares”, c’est à dire des logiciels nuisibles. Mais il n’est pas le seul, il y en a un paquet d’autres qui ne sont pas des virus : spywares (logiciels qui vous espionnent), adwares (logiciels qui vous affichent de la publicité tout le temps), ransomwares (logiciels qui prennent vos données en otage). Ce sont tous des malwares, c’est à dire des programmes nuisibles, mais pas des virus.
Généralement un virus a pour but de prendre le contrôle de votre machine, soit pour vous voler des données, soit pour se propager, soit pour l’utiliser comme machine zombie. Dans ce dernier cas, votre machine servira à un pirate qui pourra l’utiliser pour attaquer d’autres machines, envoyer du spam avec ou simplement vendre sa puissance sur Internet.
Ne pensez jamais que votre machine n’est “pas intéressante” pour les pirates sous prétexte que vous ne faites rien de particulier avec votre ordinateur. À partir du moment où votre ordinateur est connecté à Internet, il est intéressant, il peut rapporter de l’argent de bien des façons à un attaquant, même sans que l’argent vienne (directement) de vous.
Pour se protéger des virus, il faut avoir TOUS ses logiciels à jour, mais aussi éviter d’utiliser des systèmes trop sensibles aux virus. Si vous devez utiliser l’un d’entre eux (par exemple Windows), il faut rajouter un antivirus ET un firewall, même si vous ne faites qu’afficher des pages web sur des sites de confiance.
Conficker est un virus.
Un bug n’est pas un programme en plus, c’est une erreur de conception qui existe dans un des programmes qui est déjà sur votre machine.
Les machines sont stupides, elles ne font que ce qu’on leur dit. Quand vous voyez une application fonctionner, cela n’est que le résultat de millions d’instructions écrites à la main par les hommes qui ont créé vos logiciels, et ces hommes sont faillibles. Parfois, ils font une erreur dans le programme, et cette erreur peut avoir tout un cas de conséquences inattendues : le programme ne fait pas ce que vous voulez, il le fait mais mal, il est lent, il plante, etc.
Tout, absolument TOUT sur votre machine, est un programme. Et TOUT peut contenir un bug. En fait, aujourd’hui les systèmes informatiques sont tellement complexes qu’il y a forcément des bugs sur votre machine. Mais souvent ce sont des bugs discrets, qui ne sont pas très graves.
Dites-vous qu’un téléphone ou une tablette, si cela vous parait simple à utiliser, est bien plus complexe qu’un avion ou une fusée dans son fonctionnement en détail. Pas parce que c’est techniquement plus poussé, mais parce qu’il y a tellement de couches et de parties qui interagissent entre elles. Et dans ce gros fouillis il y a des erreurs, les fameux bugs.
On peut difficilement se protéger des bugs. Garder vos logiciels à jour peut aider à corriger les bugs précédents, mais parfois en ajouter des nouveaux. Les bugs font partie de la vie, aucun système n’est parfait. En revanche, plus un système est simple et maitrisé, moins il a de chance de contenir de bug. Si les bugs vous font peur, choisir un produit qui ne fait qu’une seule chose, mais bien, ou choisir un produit éprouvé, vous permet d’espérer une expérience avec moins de bugs.
Heartbleed est un bug.
Un virus utilise souvent un bug pour attaquer votre système. Le virus a en effet un objectif (au moins celui de se reproduire), et le logiciel qu’il attaque n’est généralement pas conçu pour lui permettre de l’atteindre. Le virus va donc souvent utiliser un bug du système pour obtenir ce qu’il veut.
A l’inverse, un bug n’a pas d’objectif, c’est une erreur, et donc il ne peut pas “causer un virus”. Ça ne veut rien dire.
Généralement, tout ce petit monde va donc ensemble, un virus peut exploiter un bug, dans le but d’installer un adware sur votre machine, afin de gagner de l’argent via la publicité, un keylogger pour récupérer votre numéro de carte bleue et un rootkit pour garder votre machine sous contrôle. Il y a donc 5 choses ici : le virus, le bug, l’adware, le spyware et le rootkit. Ce sont 5 choses différentes, mais la raison pour laquelle vous en entendez souvent parler en même temps, c’est qu’elles sont liées.
C’est impossible. C’est comme dire qu’il n’y a pas de fourmis dans vote jardin. Ce qui est possible, c’est qu’un bug ne vous gène pas et que vous n’en ayez vu aucun.
Vous n’avez aucune idée de ce qui est dangereux. Croyez-moi, si auparavant vous ne saviez pas la différence entre un bug et un virus, vous n’avez pas la compétence (ce n’est pas une insulte) pour savoir ce qui met en danger votre machine. Et donc, vous le faites.
Les virus modernes essayent le plus souvent d’être discrets et de ne pas vous causer de souci pour éviter que vous ayez envie de les supprimer.
Il y a des tas d’autres malwares qui causent cela. Il y a très peu de chance que ce soit un virus. Ça ne veut pas dire qu’il ne faut pas traiter votre ordinateur, mais un anti-virus ne suffira pas. Il vous faut des programmes anti-malwares plus généralistes.