Chapter 7, Basic
Select columns and rows
Almost never do you need the entire table. You need certain columns, certain rows, a piece. Pandas has a tool by label, another by position, and the difference between them is the cause of almost every selection bug.
Columns
A name returns a Series, and a list of names returns a DataFrame. The double brackets are not decoration: the outer one is "select from df" and the internal is "a list of names":
import pandas as pd
pd.set_option("display.width", 170)
pd.set_option("display.max_columns", 20)
df = pd.read_csv("dados/funcionarios_15.csv")
print(type(df["salario"]).__name__, type(df[["salario"]]).__name__)
print(df[["nome", "departamento", "salario"]].head(3))
Series DataFrame
nome departamento salario
0 Ana Souza Engenharia 4500
1 Bruno Lima Marketing R$ 4650
2 Carla Mendes engenharia 4800
A list with one name is still a list, and returns a DataFrame with one column. It's the most common trick in this part.
`.loc` and `.iloc`
The .loc selects by label and the .iloc, by integer position. In a table with the default index, they seem the same, but with another index the difference appears:
print(len(df.loc[0:3]), len(df.iloc[0:3]))
print(df.iloc[-1]["nome"])
4 3
Ana Souza
.loc[] | .iloc[] | |
|---|---|---|
| Selects by | Labels | Positions integers |
| Rows | Index labels | 0, 1, 2... |
| End of the slice | Usually included | Excluded |
The df.loc[0:3] returns 4 rows (0, 1, 2, and 3), and the df.iloc[0:3] returns 3. A slice that looks the same gives a different result, and this asymmetry catches almost everyone coming from Python slices.
An index that is not 0, 1, 2
With the id_funcionario as index, the same entry falls into different rows:
por_id = df.set_index("id_funcionario")
print(por_id.loc[103, "nome"], por_id.iloc[2]["nome"])
print(por_id.loc[103:105, "nome"].tolist())
Carla Mendes Carla Mendes
['Carla Mendes', 'Diego Rocha', 'Elisa Fernandes']
The .loc[103] searches for the label 103 (Carla), and the .iloc[2] searches for the position 2, which happens to be the same person. The slice .loc[103:105] brings the three labels, including 105. With the iloc, a number like 103 would be a position that does not exist.
An index can repeat, and here it repeats: id 101 appears twice. The .loc returns all the rows with that label, and the type of the result changes from Series to DataFrame:
print(type(por_id.loc[103]).__name__, type(por_id.loc[101]).__name__)
print(por_id.loc[101].shape)
print(por_id.index.is_unique)
Series DataFrame
(2, 9)
False
A code that assumes "one id, one row" and uses .loc[id] break (or, worse, silently passes with a result of another type) as soon as there is a repeated id. Chapter 10 resolves the repetition.
Rows and columns together
The .loc accepts rows and columns in the same call, and the condition can be a mask:
print(df.loc[df["experiencia"] > 6, ["nome", "salario"]])
print(df.loc[2:4, "nome":"cidade"])
nome salario
6 Gabriela Nunes 5400
7 Henrique Costa R$ 5550
14 Ana Souza 6600
nome departamento cidade
2 Carla Mendes engenharia Porto Alegre
3 Diego Rocha Ciência de Dados Rio de Janeiro
4 Elisa Fernandes RH NaN
For a single value, at (by label) and iat (by position) are more straightforward and faster than loc and iloc:
print(df.at[0, "nome"], df.iat[0, 1])
Ana Souza Ana Souza
Selecting also creates a new table
A selection returns an independent. Changing the result does not change the original table, and this is a guarantee of Pandas 3 (the copy on write, which Chapter 27 details):
parte = df[["nome", "idade"]]
parte.loc[0, "nome"] = "ALTERADO"
print(df.at[0, "nome"])
Ana Souza
Exercise 1
A slice by position and another by label
Write terceiro_a_quinto(tabela), which returns the rows at positions 2, 3, and 4, only with the columns nome and departamento, using iloc and loc so that the result has 3 rows and 2 columns.