Skip to content

    Chapter 14, Basic

    The complete flow, from start to finish

    All the steps you learned, chained together as in a real analysis, on the same dataset. The original course has a trap in its own script, and I show it first.

    The script that breaks

    The original course script selects only a few columns (step 3) and then tries to fill the idade and convert the data_admissao (steps 5 to 8), which it just discarded. This order fails, and it is a good example that the order of the steps is part of the analysis:

    basico/cap14_fluxo_completo.pylinhas 10 a 22
    import numpy as np
    import pandas as pd
    
    pd.set_option("display.width", 170)
    pd.set_option("display.max_columns", 20)
    
    bruto = pd.read_csv("dados/funcionarios_15.csv")
    so_essas = bruto[["id_funcionario", "nome", "departamento", "cidade", "salario",
                      "experiencia", "nota_desempenho", "projetos_concluidos"]]
    try:
        so_essas["idade"].fillna(so_essas["idade"].median())
    except KeyError as erro:
        print("KeyError:", erro)
    
    Output
    KeyError: 'idade'
    

    The rule I follow: select columns only at the end, or then to handle everything you will need from them.

    The flow in the right order

    I write the flow as a function: it receives the file path and returns the processed table and the summary. Thus, the flow is testable, repeatable, and can be called with another file:

    basico/cap14_fluxo_completo.pylinhas 27 a 73
    CANONICO = {"engenharia": "Engenharia", "marketing": "Marketing", "finanças": "Finanças",
                "ciência de dados": "Ciência de Dados", "rh": "RH"}
    
    
    def analisar(caminho: str) -> tuple[pd.DataFrame, pd.DataFrame]:
        # 1 e 2. Carregar e inspecionar
        df = pd.read_csv(caminho)
        assert df["id_funcionario"].notna().all()
    
        # 3. Corrigir os tipos (antes de qualquer conta)
        df["salario"] = pd.to_numeric(df["salario"].str.replace("R$ ", "", regex=False))
        df["data_admissao"] = pd.to_datetime(df["data_admissao"])
    
        # 4. Padronizar o texto
        df["departamento"] = df["departamento"].str.strip().str.lower().map(CANONICO)
        df["departamento"] = df["departamento"].fillna("Não informado")
        df["cidade"] = df["cidade"].fillna("Não informada")
    
        # 5. Remover repetições pelo identificador
        df = df.drop_duplicates(subset=["id_funcionario"], keep="first")
    
        # 6. Preencher os ausentes numéricos com a mediana
        df["salario"] = df["salario"].fillna(df["salario"].median())
        df["idade"] = df["idade"].fillna(df["idade"].median())
    
        # 7. Criar atributos
        df["nivel"] = pd.cut(df["experiencia"], bins=[0, 2, 5, np.inf], right=False,
                             labels=["Iniciante", "Pleno", "Sênior"]).astype(str)
    
        # 8. Selecionar e ordenar, só no final
        df = df[["id_funcionario", "nome", "departamento", "cidade", "idade", "salario", "experiencia",
                 "nota_desempenho", "projetos_concluidos", "nivel"]]
        df = df.sort_values(["nota_desempenho", "projetos_concluidos", "id_funcionario"],
                            ascending=[False, False, True]).reset_index(drop=True)
    
        # 9. Resumir por departamento
        resumo = df.groupby("departamento").agg(
            salario_medio=("salario", "mean"),
            nota_media=("nota_desempenho", "mean"),
            funcionarios=("id_funcionario", "count"),
        ).reset_index()
        return df, resumo
    
    
    tabela, resumo = analisar("dados/funcionarios_15.csv")
    print(tabela.shape, int(tabela.isna().sum().sum()))
    print(resumo.round(1))
    
    Output
    (14, 10) 0
           departamento  salario_medio  nota_media  funcionarios
    0  Ciência de Dados         5962.5         6.0             2
    1        Engenharia         5100.0         6.0             3
    2          Finanças         5925.0         4.5             2
    3         Marketing         5362.5         6.8             4
    4     Não informado         5700.0         6.0             1
    5                RH         5287.5         7.5             2
    

    All missing values have been handled (the 0 after the form), and the department "no information" got a name (Não informado) instead of disappearing from the summary. The sum of the column funcionarios closes with the 14 rows.

    The business questions

    From the processed table, each question is one or two lines:

    basico/cap14_fluxo_completo.pylinhas 78 a 83
    melhor_salario = resumo.loc[resumo["salario_medio"].idxmax()]
    melhor_nota = resumo.loc[resumo["nota_media"].idxmax()]
    print(melhor_salario["departamento"], round(float(melhor_salario["salario_medio"]), 1))
    print(melhor_nota["departamento"], round(float(melhor_nota["nota_media"]), 2))
    
    print(tabela["departamento"].value_counts().to_dict())
    
    Output
    Ciência de Dados 5962.5
    RH 7.5
    {'Marketing': 4, 'Engenharia': 3, 'RH': 2, 'Ciência de Dados': 2, 'Finanças': 2, 'Não informado': 1}
    

    Which city has the most employees? The short way (value_counts().idxmax()) returns a city, and here it hides a tie:

    basico/cap14_fluxo_completo.pylinhas 85 a 88
    por_cidade = tabela["cidade"].value_counts()
    print(por_cidade.to_dict())
    empatadas = por_cidade[por_cidade == por_cidade.max()].index.tolist()
    print(sorted(empatadas), por_cidade.idxmax() in empatadas)
    
    Output
    {'São Paulo': 3, 'Rio de Janeiro': 3, 'Porto Alegre': 3, 'Não informada': 2, 'Belo Horizonte': 2, 'Curitiba': 1}
    ['Porto Alegre', 'Rio de Janeiro', 'São Paulo'] True
    

    Three cities tie at the top, and the idxmax chose one without warning. The honest answer is the list of the tied ones. The same lesson from chapter 12 applies to the "top 5":

    basico/cap14_fluxo_completo.pylinhas 90 a 91
    top5 = tabela.head(5)
    print(top5[["nome", "nota_desempenho", "projetos_concluidos"]])
    
    Output
                  nome  nota_desempenho  projetos_concluidos
    0    Felipe Araújo                9                    7
    1    Lucas Barbosa                9                    3
    2  Elisa Fernandes                8                    6
    3     Karina Alves                8                    2
    4     João Pereira                7                   11
    

    And the question "who has more experience completes more projects?" is answered with the correlation:

    basico/cap14_fluxo_completo.pylinha 93
    print(round(float(tabela["experiencia"].corr(tabela["projetos_concluidos"])), 3))
    
    Output
    0.217
    

    The value, 0.22, indicates a positive, but weak: in this set, those with more experience tend to complete a few more projects, but experience explains little. With 14 people, it is an indication, not a conclusion.

    Exercise 1

    One more business question

    Using tabela, find out how many employees at each level (Iniciante, Pleno, Sênior) have a performance score of 8 or higher, and check that the total equals the number of rows with a score of 8 or higher.