Tableaux et calcul vectorisé
La bibliothèque NumPy, tirant son nom de l'abréviation de l'anglicisme Numerical Python, est l'un des paquets fondamentaux les plus importants pour le calcul numérique en Python. La plupart des paquets de calcul fournissant des fonctionnalités scientifiques utilisent les objets de tableau de NumPy comme langue véhiculaire pour l'échange de données.
Voici quelques-unes des fonctionnalités que vous trouverez dans NumPy :
- ndarray, un tableau multidimensionnel efficace offrant des opérations arithmétiques rapides orientées tableau et des capacités de diffusion flexibles.
- Des fonctions mathématiques pour des opérations rapides sur des tableaux entiers de données sans avoir à écrire de boucles.
- Des outils de lecture et d'écriture de données de tableau sur le disque et de travail avec des fichiers cartographiés en mémoire.
- Algèbre linéaire, génération de nombres aléatoires et capacités de transformation de Fourier.
- Une API en C pour connecter NumPy à des bibliothèques écrites en C, C++ ou FORTRAN.
Étant donné que NumPy fournit une API en langage de programmation C facile à utiliser, il est simple de transmettre des données à des bibliothèques externes écrites dans un langage de programmation de bas niveau et également pour les bibliothèques externes de renvoyer des données à Python sous forme de tableaux NumPy. Cette fonctionnalité a fait de Python un langage de prode choix pour encapsuler les bases de code C/C++/Fortran héritées et leur donner une interface dynamique et facile à utiliser.
Bien que NumPy ne fournisse pas en soi de fonctionnalités de modélisation ou scientifiques, une compréhension des tableaux NumPy et du calcul orienté tableau vous aidera à utiliser les outils avec une sémantique orientée tableau, comme pandas, beaucoup plus efficacement.
Pour la plupart des applications d'analyse de données, les principaux domaines de fonctionnalité sont :
- Opérations de tableau vectorisées rapides pour le nettoyage et le tri des données, le sous-ensemble et le filtrage, la transformation et tout autre type de calcul
- Algorithmes de tableau courants tels que les opérations de tri, d'unicité et d'ensemble
- Statistiques descriptives efficaces et agrégation/résumé des données
- Alignement des données et manipulations de données relationnelles pour fusionner et joindre des ensembles de données hétérogènes
- Expression de la logique conditionnelle sous forme d'expressions de tableau au lieu de boucles avec des branches if-elif-else
- Manipulations de données par groupe (agrégation, transformation, application de fonction)
Bien que NumPy fournisse une base informatique pour le traitement général des données numériques, de nombreux lecteurs voudront utiliser pandas comme base pour la plupart des types de statistiques ou d'analyses, en particulier sur les données tabulaires. pandas fournit également des fonctionnalités plus spécifiques au domaine, comme la manipulation de séries chronologiques, n'étant pas présente dans NumPy.
Note : Le calcul orienté tableau dans Python remonte à 1995, lorsque Jim Hugunin a créé la bibliothèque Numeric. Au cours des 10 années suivantes, de nombreuses communautés de programmation scientifique ont commencé à faire de la programmation de tableaux en Python, mais l'écosystème de la bibliothèque s'est fragmenté au début des années 2000. En 2005, Travis Oliphant a pu forger le projet NumPy à partir des projets Numeric et Numarray de l'époque pour rassembler la communauté autour d'un seul cadre de calcul de tableau.
L'une des raisons pour lesquelles NumPy est si important pour les calculs numériques en Python est qu'il est conçu pour être efficace sur de grands tableaux de données. Il y a plusieurs raisons à cela :
- NumPy entrepose les données en interne dans un bloc de mémoire contigu, indépendamment des autres objets Python intégrés. La bibliothèque d'algorithmes de NumPy écrite en langage C peut fonctionner sur cette mémoire sans aucune vérification de type ni autre surcharge. Les tableaux NumPy utilisent également beaucoup moins de mémoire que les séquences Python intégrées.
- Les opérations NumPy effectuent des calculs complexes sur des tableaux entiers sans avoir besoin de boucles for de Python.
Pour vous donner une idée de la différence de performances, considérez un tableau NumPy d'un million d'entiers et la liste Python équivalente :
Multiplions maintenant chaque séquence par 2 :
Pour obtenir :
CPU times: user 20 ms, sys: 8 ms, total: 28 msWall time: 26.5 ms
Ensuite :
Pour obtenir :
CPU times: user 408 ms, sys: 64 ms, total: 472 msWall time: 473 ms
Les algorithmes basés sur NumPy sont généralement 10 à 100 fois plus rapides (ou plus) que leurs homologues Python purs et utilisent beaucoup moins de mémoire.
Le ndarray de NumPy : un objet tableau multidimensionnel
L'une des fonctionnalités clefs de NumPy est son objet tableau N-dimensionnel, ou ndarray, étant un conteneur rapide et flexible pour les grands ensembles de données en Python. Les tableaux vous permettent d'effectuer des opérations mathématiques sur des blocs entiers de données en utilisant une syntaxe similaire aux opérations équivalentes entre éléments scalaires.
Pour vous donner une idée de la façon dont NumPy permet des calculs par lots avec une syntaxe similaire aux valeurs scalaires sur les objets Python intégrés, on importe d'abord NumPy et génère un petit tableau de données aléatoires :
- import numpy as np
-
- # Générer des données aléatoires
- data = np.random.randn(2, 3)
- data
On obtiendra un résultat ressemblant à ceci :
array([[-0.01609597, -0.31927867, 0.58715762],[ 0.2821461 , -0.76816857, -1.55024241]])
On écrit ensuite des opérations mathématiques avec des données :
- data * 10
On obtiendra un résultat ressemblant à ceci :
array([[ -0.16095966, -3.19278668, 5.87157616],[ 2.82146104, -7.68168565, -15.50242413]])
Si on écrit :
- data + data
On obtiendra un résultat ressemblant à ceci :
array([[-0.03219193, -0.63855734, 1.17431523],[ 0.56429221, -1.53633713, -3.10048483]])
Dans le premier exemple, tous les éléments ont été multipliés par 10. Dans le second, les valeurs correspondantes dans chaque «cellule» du tableau ont été additionnées les unes aux autres.
Note : L'espace de noms numpy est vaste et contient un certain nombre de fonctions dont les noms sont en conflit avec les fonctions Python intégrées (comme min et max).
Un ndarray est un conteneur multidimensionnel générique pour des données homogènes ; c'est-à-dire que tous les éléments doivent être du même type. Chaque tableau a une forme, un tuple indiquant la taille de chaque dimension et un dtype, un objet décrivant le type de données du tableau :
- data.shape
On obtiendra un résultat ressemblant à ceci :
(2, 3)Si on écrit :
- data.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('float64')Créer des ndarrays
La façon la plus simple de créer un tableau est d'utiliser la fonction array. Elle accepte tout objet de type séquence (y compris d'autres tableaux) et produit un nouveau tableau NumPy contenant les données transmises. Par exemple, une liste est un bon candidat pour la conversion :
- data1 = [5, 7.5, 8, 0, 1]
- arr1 = np.array(data1)
- arr1
On obtiendra un résultat ressemblant à ceci :
array([5. , 7.5, 8. , 0. , 1. ])Les séquences imbriquées, comme une liste de listes de longueur égale, seront converties en un tableau multidimensionnel :
- data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
- arr2 = np.array(data2)
- arr2
On obtiendra un résultat ressemblant à ceci :
array([[1, 2, 3, 4],[5, 6, 7, 8]])
Étant donné que data2 est une liste de listes, le tableau NumPy arr2 a deux dimensions avec une forme déduite des données. Nous pouvons le confirmer en inspectant les attributs ndim et shape :
- arr2.ndim
On obtiendra un résultat ressemblant à ceci :
2Ensuite :
- arr2.shape
On obtiendra un résultat ressemblant à ceci :
(2, 4)Sauf indication explicite (nous y reviendrons plus tard), np.array tente de déduire un bon type de données pour le tableau qu'il crée. Le type de données est entreposé dans un objet de métadonnées dtype spécial ; par exemple, dans les deux exemples précédents, nous avons :
- arr1.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('float64')Ensuite :
- arr2.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('int64')En plus de np.array, il existe un certain nombre d'autres fonctions permettant de créer de nouveaux tableaux. Par exemple, les zéros et les uns créent des tableaux de 0 ou de 1, respectivement, avec une longueur ou une forme donnée. empty crée un tableau sans initialiser ses valeurs à une valeur particulière. Pour créer un tableau de dimension supérieure avec ces méthodes, transmettez un tuple pour la forme :
- np.zeros(10)
On obtiendra un résultat ressemblant à ceci :
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])Ensuite :
- np.zeros((3, 7))
On obtiendra un résultat ressemblant à ceci :
array([[0., 0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0.]])
Ensuite :
- np.empty((2, 3, 2))
On obtiendra un résultat ressemblant à ceci :
array([[[0., 0.],[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.],
[0., 0.]]])
Attention : Il n'est pas prudent de supposer que np.empty renverra un tableau contenant uniquement des zéros. Dans certains cas, il peut renvoyer des valeurs «poubelles» non initialisées.
arange est une version à valeur de tableau de la fonction de l'intervalle intégrée de Python :
- np.arange(16)
On obtiendra un résultat ressemblant à ceci :
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])Consultez le tableau suivant pour obtenir une courte liste des fonctions de création de tableau standard. Étant donné que NumPy est axé sur le calcul numérique, le type de données, s'il n'est pas spécifié, sera dans de nombreux cas float64 (virgule flottante).
Fonction | Description |
---|---|
array | Convertir les données d'entrée (liste, tuple, tableau ou autre type de séquence) en un ndarray soit en déduisant un dtype, soit en spécifiant explicitement un dtype ; copie les données d'entrée par défaut. |
asarray | Convertir l'entrée en ndarray, mais ne pas copier si l'entrée est déjà un ndarray |
arange | Comme l'intervalle intégrée mais renvoie un ndarray au lieu d'une liste |
ones, ones_like | Produire un tableau de tous les 1 avec la forme et le type de données donnés ; ones_like prend un autre tableau et produit un tableau de uns de la même forme et du même type de données |
zeros, zeros_like | Comme ones et ones_like mais produisant des tableaux de 0 à la place |
empty, empty_like | Créez de nouveaux tableaux en allouant une nouvelle mémoire, mais ne les remplissez pas avec des valeurs telles que des uns et des zéros |
full, full_like | Produire un tableau de la forme et du type de données donnés avec toutes les valeurs définies sur la «valeur de remplissage» indiquée. full_like prend un autre tableau et produit un tableau rempli de la même forme et du même type de données. |
eye, identity | Créer une matrice identité carrée N × N (1 sur la diagonale et 0 ailleurs) |
Types de données pour les ndarrays
Le type de données ou dtype est un objet spécial contenant les informations (ou métadonnées, données sur les données) dont le ndarray a besoin pour interpréter un bloc de mémoire comme un type particulier de données :
- arr1 = np.array([1, 2, 3], dtype=np.float64)
- arr2 = np.array([1, 2, 3], dtype=np.int32)
- arr1.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('float64')Ensuite :
- arr2.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('int32')Les dtypes sont une source de flexibilité pour NumPy pour interagir avec des données provenant d'autres systèmes. Dans la plupart des cas, ils fournissent une cartographie directement sur un disque sous-jacent ou une représentation en mémoire, ce qui facilite la lecture et l'écriture de flux binaires de données sur le disque et permet également de se connecter à du code écrit dans un langage de programmation de bas niveau comme C ou Fortran. Les dtypes numériques sont nommés de la même manière : un nom de type, comme float ou int, suivi d'un nombre indiquant le nombre de bits par élément. Une valeur à virgule flottante double précision standard (ce qui est utilisé sous le capot dans l'objet float de Python) occupe 8 octets ou 64 bits. Ainsi, ce type est connu dans NumPy sous le nom de float64. Voir le tableau suivant pour une liste complète des types de données pris en charge par NumPy :
Type | Code de type | Description |
---|---|---|
int8, uint8 | i1, u1 | Types d'entiers 8 bits (1 octet) signés et non signés |
int16, uint16 | i2, u2 | Types d'entiers 16 bits signés et non signés |
int32, uint32 | i4, u4 | Types d'entiers 32 bits signés et non signés |
int64, uint64 | i8, u8 | Types d'entiers 64 bits signés et non signés |
float16 | f2 | Virgule flottante demi-précision |
float32 | f4 ou f | Virgule flottante simple précision standard ; compatible avec le float de C |
float64 | f8 ou d | Virgule flottante double précision standard ; compatible avec le double de C et l'objet float de Python. |
float128 | f16 ou g | Virgule flottante de précision étendue |
complex64, complex128, complex256 | c8, c16, c32 | Nombres complexes représentés respectivement par deux nombres flottants 32, 64 ou 128 |
bool | ? | Type booléen entreposant les valeurs True et False |
object | O | Type d'objet Python ; une valeur peut être n'importe quel objet Python |
string_ | S | Type de chaîne ASCII de longueur fixe (1 octet par caractère) ; par exemple, pour créer un type de chaîne d'une longueur de 10, utilisez «S10» |
unicode_ | U | Type Unicode de longueur fixe (nombre d'octets spécifique à la plateforme) ; même sémantique de spécification que string_ (par exemple, «U10») |
Remarque : Ne vous inquiétez pas de mémoriser les dtypes NumPy, surtout si vous êtes un nouvel utilisateur. Il est souvent nécessaire de se soucier uniquement du type général de données que vous traitez, qu'il s'agisse de données à virgule flottante, complexes, entières, booléennes, de chaînes ou d'objets Python généraux. Lorsque vous avez besoin de plus de contrôle sur la façon dont les données sont entreposées en mémoire et sur le disque, en particulier les grands ensembles de données, il est bon de savoir que vous avez le contrôle sur le type d'entreposage.
Vous pouvez convertir ou convertir explicitement un tableau d'un type de données à un autre en utilisant la méthode astype de ndarray :
- arr = np.array([1, 2, 3, 4, 5])
- arr.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('int64')Ensuite :
- float_arr = arr.astype(np.float64)
- float_arr.dtype
On obtiendra un résultat ressemblant à ceci :
dtype('float64')Dans cet exemple, les entiers ont été convertis en nombres à virgule flottante. Si on convertit certains nombres à virgule flottante en nombres de type entier, la partie décimale sera tronquée :
- arr = np.array([3.8, -1.2, -2.7, 0.5, 12.7, 10.1])
- arr
On obtiendra un résultat ressemblant à ceci :
array([ 3.8, -1.2, -2.7, 0.5, 12.7, 10.1])Ensuite :
- arr.astype(np.int32)
On obtiendra un résultat ressemblant à ceci :
array([ 3, -1, -2, 0, 12, 10], dtype=int32)Si vous avez un tableau de chaînes de caractères représentant des nombres, vous pouvez utiliser astype pour les convertir sous forme numérique :
- numeric_strings = np.array(['1.25', '-9.7', '42'], dtype=np.string_)
- numeric_strings.astype(float)
On obtiendra un résultat ressemblant à ceci :
array([ 1.25, -9.7 , 42. ])Attention : Il est important d'être prudent lors de l'utilisation du type numpy.string_, car les données de chaîne de caractères dans NumPy sont de taille fixe et peuvent tronquer l'entrée sans avertissement. pandas a un comportement prêt à l'emploi plus intuitif sur les données non numériques.
Si le moulage échoue pour une raison quelconque (comme une chaîne de caractères ne pouvant pas être convertie en float64), une ValueError sera générée. Ici, on aurait pu écrire float au lieu de np.float64 ; NumPy alias les types Python vers ses propres dtypes de données équivalents.
Vous pouvez également utiliser l'attribut dtype d'un autre tableau :
- int_array = np.arange(10)
- calibers = np.array([.22, .270, .358, .370, .44, .50], dtype=np.float64)
- int_array.astype(calibers.dtype)
On obtiendra un résultat ressemblant à ceci :
array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])Il existe des chaînes de code de type abrégé que vous pouvez également utiliser pour faire référence à un dtype :
- empty_uint32 = np.empty(8, dtype='u4')
- empty_uint32
On obtiendra un résultat ressemblant à ceci :
array([1635017060, 540876849, 539768411, 741682743, 539768864,824192048, 168632413, 6029312], dtype=uint32)
Remarque : L'appel de astype crée toujours un nouveau tableau (une copie des données), même si le nouveau dtype est le même que l'ancien dtype.