Chapter 4, Basic
Series: the labeled column
A `Series` is the simplest piece of Pandas: values plus a label for each one. Understanding it well avoids half the pitfalls with the `DataFrame`, which is made of them.
Create a Series
A Series can arise from a list, a dictionary, or a NumPy array. Without an explicit index, the labels are 0, 1, 2... With an index, the labels are whatever you want, and the keys of a dictionary become the index by themselves:
import numpy as np
import pandas as pd
salarios = pd.Series([4500, 6500, 5000])
print(salarios)
com_nome = pd.Series([4500, 6500, 5000], index=["Ana", "Bruno", "Carla"], name="salario")
print(com_nome)
print(pd.Series({"Ana": 4500, "Bruno": 6500}))
0 4500
1 6500
2 5000
dtype: int64
Ana 4500
Bruno 6500
Carla 5000
Name: salario, dtype: int64
Ana 4500
Bruno 6500
dtype: int64
A Series has values (the data), index (the labels) and, optionally, a name, which becomes the name of the column when it enters a DataFrame:
print(com_nome.name, com_nome.index.tolist(), com_nome.to_numpy())
salario ['Ana', 'Bruno', 'Carla'] [4500 6500 5000]
By label or by position
The index provides two ways to access a value: by label (.loc) and by position (.iloc). The two only coincide when the index is 0, 1, 2... and one should never rely on that:
print(com_nome.loc["Bruno"], com_nome.iloc[1])
print(com_nome.iloc[-1], com_nome.loc["Ana":"Bruno"].tolist())
6500 6500
5000 [4500, 6500]
Note that the slice by label ("Ana":"Bruno") includes both extremes, unlike Python slices. In Pandas 3, accessing com_nome[1] with an integer in a text index no longer falls to the position, as it did in older versions:
try:
com_nome[1]
except KeyError as erro:
print("KeyError:", erro)
KeyError: 1
Always use .loc or .iloc, which indicate what you want.
Vectorized operations, and alignment by label
The operations apply to the Series entire, as in NumPy. What changes is that the sum of two Series aligns by labels, and labels that only exist on one side become NaN:
print((com_nome * 1.1).round(0).tolist())
print((com_nome > 4800).tolist())
print(com_nome.mean())
bonus = pd.Series({"Ana": 500, "Carla": 300, "Diego": 100})
print(com_nome + bonus)
print(com_nome.add(bonus, fill_value=0))
[4950.0, 7150.0, 5500.0]
[False, True, True]
5333.333333333333
Ana 5000.0
Bruno NaN
Carla 5300.0
Diego NaN
dtype: float64
Ana 5000.0
Bruno 6500.0
Carla 5300.0
Diego 100.0
dtype: float64
Bruno has no bonus, and Diego has no salary, so the common sum gives NaN on both. The method add with fill_value=0 treats the missing side as zero, and then no one sums. This is the difference that catches most people coming from NumPy: an array sums position with position, and the Series sums label with label.
Text in a Series
The accessor .str applies text operations to all the values at once, without a loop, and let the missing ones pass:
nomes = pd.Series([" ana souza ", "BRUNO LIMA", None])
print(nomes.str.strip().str.title().tolist())
['Ana Souza', 'Bruno Lima', nan]
An accessor is a set of tools
The
.strfor text, the.dtfor dates (chapter 18) and the.catfor categories (chapter 19). Each one only works on the right type of column, and that is what keeps the API organized.
Exercise 1
Net salary per employee
Given the Series com_nome, create liquido, which deducts 10% from each salary, and check Carla's amount. Then add a bonus of 200 only for those who are in bonus (and leave the others as they were).