Skip to content

    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":

    basico/cap07_selecionar.pylinhas 10 a 17
    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))
    
    Output
    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:

    basico/cap07_selecionar.pylinhas 22 a 23
    print(len(df.loc[0:3]), len(df.iloc[0:3]))
    print(df.iloc[-1]["nome"])
    
    Output
    4 3
    Ana Souza
    
    .loc[].iloc[]
    Selects byLabelsPositions integers
    RowsIndex labels0, 1, 2...
    End of the sliceUsually includedExcluded

    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:

    basico/cap07_selecionar.pylinhas 28 a 30
    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())
    
    Output
    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:

    basico/cap07_selecionar.pylinhas 32 a 34
    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)
    
    Output
    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:

    basico/cap07_selecionar.pylinhas 39 a 40
    print(df.loc[df["experiencia"] > 6, ["nome", "salario"]])
    print(df.loc[2:4, "nome":"cidade"])
    
    Output
                  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:

    basico/cap07_selecionar.pylinha 42
    print(df.at[0, "nome"], df.iat[0, 1])
    
    Output
    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):

    basico/cap07_selecionar.pylinhas 47 a 49
    parte = df[["nome", "idade"]]
    parte.loc[0, "nome"] = "ALTERADO"
    print(df.at[0, "nome"])
    
    Output
    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.