Un tableau avec des étiquettes
Une Series pandas est un tableau à une dimension dans lequel chaque valeur porte une étiquette, conservée dans l'index de la Series. Au lieu de retenir que la position 1 est Microsoft, on demande prices["MSFT"]. Une Series a un name, un dtype comme un tableau NumPy, et elle accepte la même arithmétique sur tableaux entiers : prices * 2, prices.mean() et prices > 180 fonctionnent tous. On importe pandas par convention avec import pandas as pd ; il repose sur NumPy, vu dans les sujets précédents.
import pandas as pd
prices = pd.Series([184.25, 410.2, 171.0], index=["AAPL", "MSFT", "GOOG"], name="price")
print(prices)
# AAPL 184.25
# MSFT 410.20
# GOOG 171.00
# Name: price, dtype: float64
print(prices["AAPL"], prices.iloc[1]) # 184.25 410.2 by label, by position
print(prices.index.tolist()) # ['AAPL', 'MSFT', 'GOOG']
print(prices[prices > 180]) # keeps AAPL and MSFT, labels intact
print(prices.mean(), prices.idxmax()) # 255.15 MSFT
# A dict also builds a Series: keys become the index
print(pd.Series({"AAPL": 1, "MSFT": 2}))L'alignement : les opérations associent les étiquettes, pas les positions
Quand on combine deux Series, pandas les aligne d'abord par étiquette. Les étiquettes présentes dans une seule donnent NaN (not a number, le marqueur de valeur manquante) dans le résultat, dont l'index est l'union des deux index. C'est exactement ce qu'il faut pour des positions détenues dans des comptes différents. Utilisez a.add(b, fill_value=0) pour traiter une étiquette absente comme zéro au lieu de produire NaN. La lecture par étiquette diffère aussi de NumPy : s["MSFT"] est par étiquette, tandis que s.iloc[1] est par position, et une étiquette inexistante lève KeyError.
import pandas as pd
a = pd.Series([100, 200], index=["AAPL", "MSFT"])
b = pd.Series([50, 10], index=["MSFT", "GOOG"])
print(a + b)
# AAPL NaN
# GOOG NaN
# MSFT 250.0
print(a.add(b, fill_value=0))
# AAPL 100.0
# GOOG 10.0
# MSFT 250.0
# Selecting by a list of labels, and asking for one that is missing
prices = pd.Series([184.25, 410.2], index=["AAPL", "MSFT"])
print(prices.reindex(["MSFT", "TSLA"]).tolist()) # [410.2, nan]Tri, classement et mise en base
s.sort_values(ascending=False) ordonne par valeur en emportant les étiquettes, s.sort_index() ordonne par étiquette et s.abs() prend les valeurs absolues. Tri plus découpage donnent les plus gros mouvements : r.abs().sort_values(ascending=False).index[:3] renvoie les trois étiquettes au plus grand rendement absolu. Pour comparer des actifs cotant à des niveaux de prix différents, mettez chacun en base 100 au départ : s / s.iloc[0] * 100, qui conserve l'index et le nom. Les valeurs manquantes (NaN) sont ignorées par sum, mean et consorts ; s.isna() les repère.
import pandas as pd
r = pd.Series([0.01, -0.03, 0.02], index=["x", "y", "z"])
print(r.abs().sort_values(ascending=False))
# y 0.03
# z 0.02
# x 0.01
# dtype: float64
prices = pd.Series([184.25, 410.2, 171.0], index=["AAPL", "MSFT", "GOOG"], name="price")
print(prices / prices.iloc[0] * 100) # rebased to 100 at the first value
# AAPL 100.000000
# MSFT 222.632293
# GOOG 92.808684
# Name: price, dtype: float64
gappy = pd.Series([1.0, None, 3.0])
print(gappy.sum(), gappy.mean(), gappy.isna().tolist()) # 4.0 2.0 [False, True, False]import pandas as pd
# A stray space and a lower-case letter make "the same" symbol two labels
raw = pd.Series([1, 2], index=["AAPL", "aapl "])
print(raw.index.is_unique) # True: they look different
raw.index = raw.index.str.strip().str.upper() # clean the labels
print(raw.index.is_unique) # False: now both are "AAPL"
print(raw["AAPL"].tolist()) # [1, 2] a duplicate label returns a Series
merged = raw.groupby(level=0).sum() # combine duplicates (groupby comes soon)
print(merged.to_dict()) # {'AAPL': 3}"AAPL" et "aapl " sont des étiquettes différentes : une faute de frappe produit silencieusement des lignes NaN ; nettoyez les étiquettes (strip, upper) avant de combiner. Un index peut contenir des doublons, et alors s["AAPL"] renvoie une Series au lieu d'une valeur : vérifiez s.index.is_unique pour des données censées avoir une valeur par étiquette. Les opérations renvoient de nouvelles Series, l'original reste intact, comportement que vérifient les tests du lab.pd.Series(values, index=labels, name=...); lire par étiquettes["A"]ou par positions.iloc[0].- Les opérations s'alignent sur les étiquettes (index union,
NaNsi absent) ;a.add(b, fill_value=0)traite un trou comme 0. sort_values,abs,idxmax,s / s.iloc[0] * 100; les résultats sont de nouvelles Series, l'original reste inchangé.
À suivre : les DataFrames pandas, des tableaux faits de Series qui partagent un même index.
Lab : ce qui est noté
- Étiqueter des prix par symbole avec
price_series25 pts - Additionner deux comptes par étiquette avec
total_exposure25 pts - Trouver les plus gros mouvements avec
top_movers25 pts - Mettre chaque prix en base 100 avec
rebase_to_10025 pts