Sélectionner des colonnes et des lignes — par nom ou par position
Cibler une partie d'une table : les colonnes avec des crochets, les lignes par étiquette avec loc et par position avec iloc, les cellules individuelles avec at — et savoir laquelle des deux approches vous utilisez, car après un découpage ou un tri, elles cessent de coïncider.
Comment voulez-vous apprendre ?
Le problème que nous résolvons
Vous avez lu le fichier de commandes du magasin et vous l'avez vérifié, comme le chapitre trois vous l'a enseigné. Maintenant, les gens commencent à vous poser des questions.
L'équipe des stocks dit : « Envoyez-nous juste le produit et la quantité de chaque commande — rien d'autre. » Le comptable demande : « C'était quoi exactement la commande 1006, et combien ça coûtait ? » Et votre responsable dit : « Montrez-moi les trois dernières commandes reçues. »
Aucune de ces questions n'est un calcul sur toute la table. Chacune consiste à pointer : vers certaines colonnes, vers une ligne, vers les dernières lignes, vers une seule cellule. Imprimer tout et lire avec le doigt fonctionne pour huit lignes. Ça ne fonctionne pas pour huit mille, et ça ne fonctionne pas à l'intérieur d'un programme, qui n'a pas de doigt.
Pandas offre plusieurs façons de pointer, et la raison pour laquelle il en existe plusieurs est le vrai sujet de ce chapitre. Une ligne peut être désignée de deux façons différentes : par son nom (son étiquette dans l'index) ou par sa position (première, deuxième, dernière). Quand une table vient d'être lue, ces deux façons donnent par hasard les mêmes numéros, ce que les débutants ne remarquent jamais. Dès que vous prenez les trois dernières lignes, ou que vous triez, ou que vous filtrez, elles cessent de coïncider — et le code qui les confondait commence à renvoyer la mauvaise ligne sans la moindre erreur.
Ce chapitre porte donc sur la sélection, mais surtout sur le fait de toujours savoir laquelle des deux méthodes vous utilisez.
À la fin de ce chapitre, vous saurez
- Sélectionner une colonne et obtenir une
Series, ou plusieurs et obtenir unDataFrame— et savoir lequel vous manipulez - Sélectionner des lignes par étiquette avec
.locet par position avec.iloc, et expliquer pourquoi leurs tranches se terminent différemment - Sélectionner des lignes et des colonnes ensemble avec
.loc[lignes, colonnes]et prédire la forme du résultat — jusqu'à une valeur unique avec.locou.at - Donner aux lignes des étiquettes significatives avec
set_index(), et voir comment cela change la signification de.loc - Expliquer pourquoi modifier une sélection ne modifie pas la table d'origine sous pandas 3, et modifier l'original correctement ; lire et corriger les messages
KeyErroretIndexErrorque la sélection produit
Prérequis : Lire un CSV et vérifier ce qui a été chargé.
Créer le fichier d'abord
Dans votre dossier de projet, créez orders.csv avec exactement ces neuf lignes. Ce fichier sera utilisé pour le reste du cours, alors enregistrez-le quelque part où vous le retrouverez :
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0Chaque ligne est une commande : un numéro de commande, la date, la succursale du magasin qui l'a vendue, ce qui a été vendu, la catégorie du produit, la quantité, et le prix unitaire.
Avant d'écrire le code
La sélection semble si simple que les gens commencent à taper des crochets immédiatement. Prenez d'abord une minute pour ces étapes, parce que chaque erreur de sélection dans ce chapitre vient du fait d'en avoir sauté une.
1. Formulez la question en une phrase et soulignez les noms. Les noms vous disent ce vers quoi vous pointez :
- « le produit et la quantité de chaque commande » — deux colonnes, toutes les lignes
- « la commande 1006 » — une ligne, nommée par son numéro de commande
- « les trois dernières commandes » — lignes par position, comptées depuis la fin
- « le prix de la commande 1006 » — une cellule : une ligne et une colonne ensemble
2. Regardez la table avant de pointer dedans. Vous ne pouvez pointer que vers des noms qui existent. Trois choses comptent pour la sélection : la taille, les noms exacts des colonnes, et l'index — les étiquettes sur la gauche.
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(list(orders.columns))
print(orders.index)(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)Les noms de colonnes reviennent sous forme de liste, entre guillemets — vous pouvez ainsi voir que 'price' est en minuscules et n'a pas d'espaces parasites. L'index est un RangeIndex allant de 0 à 7 : les étiquettes sont simplement un compteur. Retenez-le, car c'est la coïncidence sur laquelle repose tout ce chapitre. Pour l'instant, la ligne étiquetée 2 est aussi la ligne en position 2. Ce ne sera pas toujours vrai.
3. Esquissez la réponse avant de la produire. Qu'est-ce qui doit ressortir — un seul nombre, une colonne, ou une table plus petite ? Combien de lignes et de colonnes ? Pour la demande de l'équipe des stocks, la réponse est une table de 8 lignes et 2 colonnes. Pour « la commande 1006 », c'est une ligne. Pour « le prix de la commande 1006 », c'est un seul nombre, 850.0. Si vous connaissez la forme attendue, une mauvaise sélection est évidente dès que vous voyez le résultat.
4. Choisissez l'outil en fonction de ce que vous savez. Cette liste résume le chapitre en une image :
- Vous connaissez les noms de colonnes → crochets :
orders["quantity"],orders[["product", "quantity"]] - Vous connaissez l'étiquette d'une ligne, son nom dans l'index →
.loc:orders.loc[2] - Vous connaissez la position d'une ligne — première, dernière, cinquième →
.iloc:orders.iloc[-3:] - Vous avez besoin de lignes et colonnes ensemble →
.loc[lignes, cols]ou.iloc[lignes, cols]:orders.loc[2:4, ["product", "price"]] - Vous avez besoin d'exactement une cellule →
.at[ligne, col]ou.loc[ligne, col]:orders.at[5, "price"]
5. Décidez comment vous allez vérifier. Après toute sélection, imprimez .shape ou type() du résultat et comparez avec votre esquisse. C'est une seule ligne, et elle détecte la plupart des erreurs ci-dessous avant qu'elles ne deviennent de mauvais résultats.
Le reste du chapitre passe en revue ces outils un par un, chacun avec son propre exemple résolu.
Une colonne : un nom entre crochets
Mettez un nom de colonne entre crochets et vous obtenez cette colonne :
quantity = orders["quantity"]
print(type(quantity))
print(quantity)<class 'pandas.Series'>
0 12
1 5
2 2
3 7
4 30
5 1
6 20
7 4
Name: quantity, dtype: int64Ce qui revient est une Series — exactement l'objet du chapitre deux : des valeurs, un index sur la gauche, un Name (la colonne d'où elle provient) et un dtype. Remarquez que l'index est venu avec. Les 0 à 7 ne sont pas décoratifs : ils disent encore à quelle ligne appartient chaque nombre.
Comme c'est une Series, toutes les méthodes de Series fonctionnent directement sur elle :
print(orders["quantity"].sum())81C'est la raison habituelle de sélectionner une seule colonne : pour lui poser une question.
Plusieurs colonnes : une liste à l'intérieur des crochets
Pour l'équipe des stocks, vous avez besoin de deux colonnes. Mettez une liste de noms à l'intérieur des crochets :
stock = orders[["product", "quantity"]]
print(type(stock))
print(stock)<class 'pandas.DataFrame'>
product quantity
0 pen 12
1 notebook 5
2 bag 2
3 bottle 7
4 eraser 30
5 bag 1
6 pen 20
7 bottle 4Les doubles crochets ne sont pas une syntaxe spéciale, et cela vaut la peine de le voir clairement. La paire externe est la sélection ; la paire interne est une liste Python ordinaire, ["product", "quantity"]. Vous pourriez construire la liste d'abord et la passer — wanted = ["product", "quantity"] puis orders[wanted] — et cela signifierait exactement la même chose. Les colonnes reviennent dans l'ordre de la liste, donc c'est aussi ainsi que vous réordonnez les colonnes.
Le résultat est un DataFrame, parce qu'une table avec deux colonnes est une table. Et voici une distinction qui piège les gens pendant des mois : une liste avec un seul nom donne quand même un DataFrame.
print(orders["product"].shape)
print(orders[["product"]].shape)(8,)
(8, 1)(8,) est une Series — une dimension, huit valeurs. (8, 1) est un DataFrame avec huit lignes et une colonne. Ils s'affichent différemment, ils ont des méthodes différentes, et quand vous les enregistrez dans un fichier, le DataFrame conserve son en-tête de colonne. La règle est simple : un nom donne une Series ; une liste donne un DataFrame — quel que soit le nombre de noms dans la liste.
Pourquoi pas orders.quantity ?
Vous verrez l'accès aux colonnes écrit avec un point dans de nombreux tutoriels :
print(orders.branch.head(2))0 north
1 south
Name: branch, dtype: strÇa fonctionne ici, et c'est plus court. Mais ça ne fonctionne que lorsque le nom de colonne est par hasard un nom Python valide qui n'est pas déjà utilisé pour autre chose — et un DataFrame possède des centaines de méthodes et d'attributs. Voici une petite table dont les colonnes s'appellent count et size, deux noms parfaitement raisonnables pour un magasin :
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})
print(d.size)
print(d["size"])4
0 3
1 4
Name: size, dtype: int64d.size ne vous a pas donné la colonne du tout. Il vous a donné l'attribut size propre au DataFrame — le nombre de cellules, 2 × 2 = 4 — en silence. Pas d'erreur, juste une mauvaise réponse. Un nom avec un espace, comme unit price, ne peut pas du tout être écrit avec un point. Les crochets n'ont aucun de ces problèmes, donc la règle pour le code que vous conservez est : utilisez df["col"]. Le point convient pour un coup d'œil rapide dans le terminal, rien de plus.
Lignes par étiquette : .loc
Les lignes ne se sélectionnent pas avec des crochets simples. (Des crochets simples avec un nom signifient « colonne », et pandas ne va pas deviner ce que vous vouliez désigner.) Pour les lignes, il existe deux outils dédiés, et le premier est .loc — localisation par étiquette (label-based location). Vous lui donnez l'étiquette de l'index :
print(orders.loc[2])order_id 1003
date 2024-03-02
branch north
product bag
category accessories
quantity 2
price 850.0
Name: 2, dtype: objectUne seule ligne est renvoyée sous la forme d'une Series, pivotée sur le côté : les noms de colonnes sont devenus son index, et son Name est l'étiquette de la ligne, 2.
Regardez le dtype: object. Une colonne a un seul type, mais une ligne traverse différentes colonnes — un nombre, du texte, un décimal — donc le seul type capable de tous les contenir est le type général object. C'est une raison pour laquelle pandas pense en colonnes : une ligne mélange les types, une colonne ne le fait pas.
Maintenant la partie importante. Demandez à .loc une plage d'étiquettes :
print(orders.loc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0
4 1005 2024-03-03 north eraser stationery 30 8.0Trois lignes : 2, 3 et 4. Une tranche .loc inclut sa fin. Cela surprend tous ceux qui connaissent Python, où range(2, 4) et list[2:4] s'arrêtent avant 4. Ce n'est pas une incohérence ; il y a une raison.
Les étiquettes ne sont pas toujours des nombres. Si les étiquettes étaient des noms de produits, vous écririez quelque chose comme loc["bag":"eraser"]. Pour exclure la fin, vous devriez nommer l'étiquette après "eraser" — et avec des noms, il n'y a pas de « suivant » que vous puissiez connaître sans regarder. Donc avec des étiquettes, vous indiquez la première que vous voulez et la dernière que vous voulez, et vous obtenez les deux. Les positions sont différentes : la position suivante après 4 est toujours 5, donc la règle Python de « s'arrêter avant » ne pose aucun problème ici.
Lignes par position : .iloc
Le second outil est .iloc — localisation par entier (integer location). Il ignore complètement les étiquettes et compte les places, depuis 0, exactement comme une liste Python :
print(orders.iloc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0Deux lignes, positions 2 et 3 ; la position 4 est exclue, comme dans tout Python. Mettez les deux résultats côte à côte : loc[2:4] a donné trois lignes et iloc[2:4] en a donné deux, sur la même table, avec les mêmes nombres entre les crochets.
Parce que .iloc est positionnel, les nombres négatifs comptent depuis la fin — ce qui répond directement à la question de votre responsable :
print(orders.iloc[-3:])order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0« Les trois dernières » est une question de position, donc .iloc est l'outil qui y correspond. .loc n'a aucune idée de ce que « dernier » signifie ; il ne connaît que les noms.
Les étiquettes et les positions ne sont pas la même chose
Jusqu'à présent, l'étiquette 2 et la position 2 étaient la même ligne, donc vous ne pouviez pas distinguer les deux outils par leurs résultats. Isolez les trois dernières commandes dans leur propre table, et la différence apparaît :
last3 = orders.tail(3)
print(last3)order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0Les lignes ont conservé leurs étiquettes : 5, 6, 7. Pandas ne les renumérote pas, intentionnellement — l'étiquette est la façon dont une ligne reste la même ligne après que vous avez découpé la table. Demandez maintenant « la première ligne » des deux façons :
print(last3.iloc[0])order_id 1006
date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 5, dtype: objectlast3.loc[0]KeyError: 0.iloc[0] signifie « la ligne en première place », qui est la commande étiquetée 5. .loc[0] signifie « la ligne dont l'étiquette est 0 » — et aucune ligne de ce type n'existe dans cette table, donc pandas lève une KeyError. C'est le piège classique qui vous rattrapera plus tard : après avoir filtré des lignes ou les avoir triées — deux notions abordées plus loin dans le cours — les positions changent et les étiquettes ne changent pas. La règle à retenir :
- Vous demandez par position — première, dernière, les trois premières après un tri ? Utilisez
.iloc. - Vous demandez par identité — cette commande, ce produit ? Utilisez
.loc.
Et le pire des cas n'est pas la KeyError. Le pire, c'est quand l'étiquette 0 existe ailleurs dans la table : alors .loc[0] renvoie silencieusement une ligne différente de celle que vous vouliez.
.locdemande un nom,.ilocdemande une position. Sur une table fraîchement lue les deux coïncident par hasard, donc le code qui les mélange passe toutes les vérifications du premier jour — et renvoie la mauvaise ligne, sans erreur, dès que la table a été découpée, filtrée ou triée.
Donner des étiquettes significatives aux lignes : set_index
Le comptable s'est renseigné sur la commande 1006. Avec l'index par défaut, vous devriez savoir que la commande 1006 se trouve sous l'étiquette 5 — une caractéristique du fichier, pas de la commande. Le vrai nom d'une ligne est son numéro de commande, alors faites du numéro de commande l'index :
by_id = orders.set_index("order_id")
print(by_id.head(3))
print(by_id.shape)date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0
1003 2024-03-02 north bag accessories 2 850.0
(8, 6)Trois choses ont changé. Les numéros de commande se sont déplacés sur la gauche, dans l'index. L'index a maintenant un nom, order_id, imprimé sur sa propre ligne au-dessus. Et la forme est (8, 6) au lieu de (8, 7) — order_id n'est plus une colonne, c'est l'index. Notez aussi que set_index a renvoyé une nouvelle table, que nous avons nommée by_id ; orders lui-même est intact.
Maintenant .loc parle la langue de la question :
print(by_id.loc[1006])date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 1006, dtype: objectEt une tranche d'étiquettes est maintenant une plage de numéros de commande, les deux extrémités incluses :
print(by_id.loc[1003:1005, ["product", "quantity"]])product quantity
order_id
1003 bag 2
1004 bottle 7
1005 eraser 30.iloc n'a pas changé du tout — les positions sont des positions, quelles que soient les étiquettes :
print(by_id.iloc[0:2])date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0Ici les deux outils se sont clairement séparés : by_id.loc[0] serait maintenant une KeyError, parce que 0 n'est pas un numéro de commande, tandis que by_id.iloc[0] est la commande 1001. Si vous avez besoin de récupérer le numéro de commande comme colonne, by_id.reset_index() le remet en place.
Choisissez une colonne dont les valeurs sont uniques. Un index est censé nommer des lignes, et un nom partagé par deux lignes ne pointe pas vers une seule. Regardez ce qui se passe avec product, qui se répète :
by_product = orders.set_index("product")
print(by_product.index.is_unique)
print(by_product.loc["bag"])False
order_id date branch category quantity price
product
bag 1003 2024-03-02 north accessories 2 850.0
bag 1006 2024-03-03 south accessories 1 850.0Pas d'erreur — mais loc["bag"] a renvoyé un DataFrame de deux lignes au lieu d'une ligne sous forme de Series. Du code qui s'attendait à une seule ligne (par exemple, lire son price comme un seul nombre) reçoit maintenant deux lignes. C'est pourquoi index.is_unique vaut une ligne de vérification chaque fois que vous définissez un index avec lequel vous comptez rechercher des lignes.
Lignes et colonnes ensemble
.loc et .iloc acceptent tous deux deux éléments séparés par une virgule : les lignes d'abord, les colonnes ensuite. Chaque partie peut être une seule étiquette, une liste ou une tranche :
print(orders.loc[2:4, ["product", "price"]])product price
2 bag 850.0
3 bottle 120.0
4 eraser 8.0print(orders.loc[[0, 3], ["branch", "product"]])branch product
0 north pen
3 east bottleUne tranche fonctionne aussi pour les colonnes, avec la règle inclusive de .loc — « toutes les colonnes de branch à category ». Un : seul signifie « toutes les lignes » :
print(orders.loc[:, "branch":"category"])branch product category
0 north pen stationery
1 south notebook stationery
2 north bag accessories
3 east bottle accessories
4 north eraser stationery
5 south bag accessories
6 east pen stationery
7 north bottle accessories.iloc fait de même par position. Les colonnes sont aussi numérotées, depuis 0, donc branch est la colonne 2 et product est la colonne 3 :
print(orders.iloc[[0, 3], [2, 3]])branch product
0 north pen
3 east bottleMême résultat que la version .loc deux blocs plus haut — mais remarquez combien c'est plus difficile à lire. [2, 3] ne dit rien ; ["branch", "product"] dit tout, et fonctionne encore si quelqu'un ajoute une colonne au début du fichier. Préférez les noms quand vous les connaissez.
Quelles dimensions obtiendrez-vous ?
Que vous obteniez une valeur unique, une Series ou un DataFrame dépend uniquement de ce que vous mettez de chaque côté de la virgule. Une seule étiquette supprime cette dimension ; une liste ou une tranche la conserve :
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)float64
Series
Series
DataFrame- une étiquette, une étiquette → une valeur unique
- une étiquette, une liste ou une tranche → une
Series(une ligne) - une liste ou une tranche, une étiquette → une
Series(une colonne) - une liste ou une tranche, une liste ou une tranche → un
DataFrame
C'est l'étape 3 de la planification — esquisser la réponse — transformée en règle. Si vous voulez une table en retour, même d'une seule ligne, mettez une liste des deux côtés : by_id.loc[[1006], ["price"]].
Une valeur : .loc et .at
La question du comptable — combien a coûté la commande 1006 ? — est une cellule : une étiquette de ligne et un nom de colonne.
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])850.0
850.0Les deux donnent le même nombre. .at n'accepte qu'une seule ligne et une seule colonne, rien d'autre, ce qui le rend légèrement plus rapide et rend votre intention évidente : c'est une seule cellule. (.iat est son jumeau positionnel, by_id.iat[5, 5].) Utilisez celui qui se lit le mieux ; .loc convient parfaitement.
Une valeur unique est un nombre ordinaire, vous pouvez donc l'utiliser directement dans un calcul. La valeur de la commande 1004 — quantité fois prix :
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])840.0Une sélection est son propre objet
Il y a encore une chose à comprendre avant d'utiliser des sélections dans de vrais programmes : ce qui se passe quand vous en modifiez une.
import pandas as pd
orders = pd.read_csv("orders.csv")
prices = orders["price"]
prices[0] = 999.0
print(prices.head(2))
print(orders.loc[0, "price"])0 999.0
1 60.0
Name: price, dtype: float64
15.0prices a changé. orders n'a pas changé. Dans pandas 3, chaque sélection se comporte comme une copie indépendante : vous pouvez la modifier librement et la table d'origine n'est jamais touchée. (En interne, pandas évite en réalité de copier les données jusqu'à ce que vous y écriviez — c'est le nom du mécanisme, Copy-on-Write — mais la règle dont vous avez besoin est le comportement, pas le mécanisme.)
La même chose vaut pour une sélection de plusieurs colonnes :
stock = orders[["product", "price"]]
stock["price"] = 0
print(orders["price"].head(2))0 15.0
1 60.0
Name: price, dtype: float64C'est le comportement sûr : une fonction qui prend une sélection et la modifie ne peut pas endommager votre table principale à votre insu. Mais cela a une conséquence que vous devez connaître. Si vous voulez modifier l'original, vous devez le faire sur l'original, en une seule étape, avec .loc :
orders.loc[0, "price"] = 16.0
print(orders.loc[0, "price"])16.0La version tentante en deux étapes — sélectionner une colonne, puis définir une cellule dedans — ne fonctionne pas, et pandas 3 vous le dit :
orders["price"][0] = 1.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(orders.loc[0, "price"])16.0Toujours 16.0 — l'affectation s'est appliquée à une copie temporaire qui a été jetée. orders["price"] a produit une sélection, et [0] = 1.0 a modifié celle-là, pas orders. C'est ce qu'on appelle l'affectation chaînée (chained assignment) : deux paires de crochets à la suite à gauche de =. La correction est toujours celle que l'avertissement nomme : un seul .loc avec à la fois la ligne et la colonne, orders.loc[0, "price"] = 1.0.
Si vous lisez des tutoriels plus anciens, vous verrez ce sujet abordé avec un SettingWithCopyWarning et des conseils disant que parfois l'original changeait et parfois non. C'était pandas 1 et 2. Dans pandas 3, la règle est simple et toujours la même : une sélection ne modifie jamais l'original ; .loc sur l'original le fait.
Un exemple complet
Les trois demandes du début du chapitre, traitées dans un seul programme. select_orders.py :
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check what arrived before pointing into it
print("Shape :", orders.shape)
print("Columns:", list(orders.columns))
# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()
# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()
# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()
# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])Shape : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True
Stock view: (8, 2)
product quantity
0 pen 12
1 notebook 5
2 bag 2
product bag
quantity 1
price 850.0
Name: 1006, dtype: object
Order 1006 total: 850.0
branch product quantity
order_id
1006 south bag 1
1007 east pen 20
1008 north bottle 4Pourquoi c'est écrit ainsi :
- Il regarde avant de pointer.
shapeetlist(columns)sont imprimés en premier, de sorte qu'un nom de colonne mal orthographié ou avec des espaces parasites serait visible avant qu'une sélection échoue. - Il vérifie que l'index est unique avant de l'utiliser pour rechercher des commandes, de sorte que
by_id.loc[1006]est garanti d'être une seule ligne etby_id.at[...]un seul nombre. - Chaque demande utilise l'outil qui correspond à sa question. Les colonnes par nom avec des crochets ; une commande par son identité avec
.locet.at; « les trois dernières » par position avec.iloc. - Il imprime la forme de la vue des stocks —
(8, 2)est exactement l'esquisse : toutes les commandes, deux colonnes. - La dernière sélection est celle à améliorer.
[1, 2, 4]signifie par hasardbranch,product,quantitydansby_id— notez que les positions ont été décalées d'un rang parce queorder_idest devenu l'index. Les positions sont fragiles. Dans votre propre code,by_id.iloc[-3:][["branch", "product", "quantity"]]ouby_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]]exprimeraient la même chose avec des noms.
Quand ça plante
KeyError: 'Price' Il n'y a pas de colonne avec exactement ce nom. Les noms de colonnes sont sensibles à la casse : price et Price sont des noms différents. Imprimez list(df.columns) et copiez le nom depuis là. Avec une liste de noms, orders[["product", "Price"]], le message devient KeyError: "['Price'] not in index" et indique exactement lequel est manquant ; avec un point, orders.Price, c'est AttributeError: 'DataFrame' object has no attribute 'Price'. Même cause, même correction.
KeyError: 'price' — alors que price est clairement dans le fichier L'en-tête a probablement des espaces après ses virgules, order_id, product, price, et ces espaces font désormais partie des noms :
from io import StringIO
import pandas as pd
raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))
print(list(padded.columns))
padded["price"]['order_id', ' product', ' price']
KeyError: 'price'La liste montre l'espace de tête dans ' price', ce qui explique exactement pourquoi le chapitre trois vous avait dit d'imprimer les colonnes sous forme de liste. Corrigez-le lors de la lecture, avec pd.read_csv("file.csv", skipinitialspace=True), ou ensuite avec df.columns = df.columns.str.strip().
KeyError: ('product', 'price') Vous avez écrit orders["product", "price"] — une seule paire de crochets. Sans la liste intérieure, Python passe les deux noms comme un seul tuple, et pandas cherche une colonne nommée ('product', 'price'). Écrivez orders[["product", "price"]].
KeyError: 0 avec .loc Vous avez utilisé une position avec .loc. Soit la table a été découpée (tail, un filtre, un tri) et l'étiquette 0 n'existe plus, soit vous avez défini un index et les étiquettes sont maintenant des numéros de commande. Si vous voulez dire « première ligne », écrivez .iloc[0]. Sur une table dont l'index est du texte, la même erreur commise avec une tranche — orders.set_index("product").loc[0:2] — lève une TypeError: cannot do slice indexing on Index with these indexers [0] of type int à la place ; la correction est la même, .iloc[0:2].
IndexError: single positional indexer is out-of-bounds .iloc a reçu une position qui n'existe pas — orders.iloc[8] sur une table de huit lignes, dont les positions vont de 0 à 7. Vérifiez df.shape ; la dernière ligne est toujours df.iloc[-1]. Le même message apparaît pour une position de colonne trop grande, orders.iloc[:, 7].
ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types Vous avez donné à .iloc un nom de colonne : orders.iloc[0, "branch"]. .iloc ne prend que des positions. Utilisez .loc avec une étiquette — orders.loc[0, "branch"] — ou, si vous avez vraiment besoin de la position, recherchez-la : orders.columns.get_loc("branch") renvoie 2.
Étape 4 sur 6 — Prédire
Vérifiez votre compréhension
Même table, mêmes nombres entre les crochets. Qu'est-ce qui est affiché ?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
print(len(orders.loc[2:5]), len(orders.iloc[2:5]))- A4 4
- B3 4
- C3 3
- D4 3
Le but est d'afficher le produit de la première des trois dernières commandes. Que se passe-t-il réellement, et pourquoi ?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
last3 = orders.tail(3)
# Wanted: the product of the first of these three orders
print(last3.loc[0, "product"])- ALe programme affiche `bag`, le produit de la première des trois lignes
- B`IndexError` — la table ne comporte que trois lignes
- C`KeyError: 0` — `tail(3)` a conservé les étiquettes 5, 6 et 7, et `.loc` recherche une étiquette 0
- DLe programme affiche `pen`, le produit de la première ligne de la table originale
Vous avez besoin d'un DataFrame contenant uniquement la colonne price — pas d'une Series. Quelle ligne vous permet d'obtenir cela ?
- Aorders.loc[:, "price"]
- Borders.price
- Corders["price"]
- Dorders[["price"]]
Répondre nécessite un compte
Connectez-vous pour vérifier vos réponses
Les questions sont ci-dessus, et les résoudre de tête est ce qui compte vraiment. Connectez-vous pour voir les réponses, les explications et les indices à trois niveaux.
À vous de jouer
En utilisant le même orders.csv, écrivez select.py. Avant tout code, écrivez en commentaire ce que chaque sélection doit retourner — une valeur, une Series ou un DataFrame — et combien de lignes. Le programme doit remplir quatre conditions :
- Les colonnes sont toujours sélectionnées par nom : imprimez la
shapededate,branchetquantityensemble. - Les commandes sont recherchées par identité dans une table indexée par
order_id, après avoir vérifié que l'index est unique : imprimez la ligne entière pour la commande 1007, puisproductetbranchpour les commandes 1002 à 1005 — les quatre. - Pour « premier » et « dernier », on procède par position : imprimez
productetpricepour les deux premières et les deux dernières commandes ; puis, avec.at, la quantité de la commande 1005 et la valeur totale de la commande 1003 (quantité × prix). - Changez le prix de la commande 1001 à
16.0dans la table indexée avec un seul.loc, et imprimez-le pour prouver que le changement a eu lieu.
Votre sortie doit être exactement ceci :
(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Ensuite prédisez, notez votre prédiction, et seulement ensuite exécutez chacune de ces trois lignes :
by_id.loc[0]orders.iloc[8]by_id["price"][1001] = 99.0— et ensuite, imprimez à nouveau le prix de la commande 1001
Solution
Planifiez d'abord. Le bloc de commentaires est l'esquisse de l'étape 3 de « Avant d'écrire le code » — ce que chaque sélection doit retourner :
- Trois colonnes →
orders[[...]]→ unDataFramede forme(8, 3) - Une commande, par identité →
by_id.loc[1007]→ uneSeries(une ligne) - Quatre commandes par identité, deux colonnes →
by_id.loc[1002:1005, [...]]→ unDataFrame4 × 2, parce que.locinclut 1005 - Deux premières et deux dernières, par position →
.iloc[:2]et.iloc[-2:]→ deuxDataFrames 2 × 2 - Cellules individuelles →
.at→ des nombres simples - Modifier l'original → un
.locsurby_id→ la table elle-même change
select.py :
import pandas as pd
orders = pd.read_csv("orders.csv")
# Columns by name -> a DataFrame of 8 rows, 3 columns
print(orders[["date", "branch", "quantity"]].shape)
# Rows named by their order number; check the names are unique first
by_id = orders.set_index("order_id")
print("unique:", by_id.index.is_unique)
# One order by identity -> a Series; four orders -> 4 x 2 (loc includes 1005)
print(by_id.loc[1007])
print(by_id.loc[1002:1005, ["product", "branch"]])
# First two and last two by place -> .iloc for rows, names for columns
print(by_id.iloc[:2][["product", "price"]])
print(by_id.iloc[-2:][["product", "price"]])
# Single cells -> plain numbers
print("qty of 1005:", by_id.at[1005, "quantity"])
print("total of 1003:", by_id.at[1003, "quantity"] * by_id.at[1003, "price"])
# Change the original, in one step
by_id.loc[1001, "price"] = 16.0
print("price of 1001:", by_id.at[1001, "price"])(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Chaque résultat correspond au plan : (8, 3) pour trois colonnes, quatre commandes pour 1002:1005 parce que .loc inclut sa fin, et deux lignes chacune pour les sélections positionnelles. Dans la condition 3, les lignes ressortent par position et les colonnes par nom — .iloc pour les lignes, puis une liste de noms — donc le code dit ce qu'il veut dire.
Maintenant les trois prédictions :
by_id.loc[0]KeyError: 0Les étiquettes sont maintenant des numéros de commande ; il n'y a pas de commande 0. La première ligne par position serait by_id.iloc[0].
orders.iloc[8]IndexError: single positional indexer is out-of-boundsHuit lignes correspondent à des positions de 0 à 7. La dernière ligne est orders.iloc[7], ou mieux, orders.iloc[-1].
by_id["price"][1001] = 99.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(by_id.at[1001, "price"])16.0Toujours 16.0 : by_id["price"] a créé une sélection séparée et le 99.0 a été affecté à celle-ci, pas à by_id. La condition 4 a fonctionné et celle-ci non, et la différence est un .loc versus deux paires de crochets à la suite.
Étape 6 sur 6
Défi — le quiz du chapitre
Dix questions, de la plus facile à la plus difficile. Les dernières sont difficiles exprès.
Connectez-vous pour faire le quiz