Skip to content

    Chapter 3, Environment

    Notebooks, scripts, and the workflow

    Data analysis is exploration: you test an idea, look at the result, and decide the next step. Notebooks exist for this cycle, and they have a trap that everyone falls into once.

    Code for this chapter: ambiente/cap03_notebooks.py

    Script or notebook

    A script runs from top to bottom, from start to finish, every time. This is great for a program you will run on the server, but bad for exploring data, because reprocessing everything to look at one column is slow. A notebook is divided into cells that you execute one at a time, and the result is visible right below, like in a lab notebook.

    OptionWhen I use
    JupyterLabLocal exploration, the standard of the area
    Jupyter in VS CodeNotebook within the same editor where I write the rest of the code
    Google ColabWhen I don't want to install anything, or need a GPU
    Common scriptWhen the code will become routine, test, or service

    To use JupyterLab in a project with uv, install it as a development dependency and open:

    Terminal
    uv add --dev jupyterlab ipykernel
    uv run jupyter lab
    

    The kernel and the hidden state trap

    Each notebook is linked to a kernel, the Python process that remembers all the variables that you created. The cells do not need to run in order: you can execute the fifth, go back, and re-execute the second. This is exactly what causes the problem. The notebook may show a result that only exists because of a cell you edited later, and the text you read on the screen no longer reproduces the result:

    ambiente/cap03_notebooks.pylinhas 10 a 23
    celulas = ["total = 10", "dobro = total * 2"]
    
    kernel = {}
    for celula in celulas:
        exec(celula, kernel)
    
    celulas[0] = "total = 99"
    exec(celulas[0], kernel)
    print("o que a tela mostra:", kernel["dobro"])
    
    novo_kernel = {}
    for celula in celulas:
        exec(celula, novo_kernel)
    print("depois de reiniciar e rodar tudo:", novo_kernel["dobro"])
    
    Output
    o que a tela mostra: 20
    depois de reiniciar e rodar tudo: 198
    

    I edited the first cell to 99 and re-ran just it. The dobro on the screen remained 20, a value that no longer corresponds to the visible code. Only by restarting the kernel and running everything in order does the true value appear, 198.

    The rule before calling a notebook ready

    Restart the kernel and run all the cells, in order. If something breaks or changes, the notebook depended on a hidden state. I do this before sharing any notebook.

    The best of both worlds: cells in a `.py` file

    VS Code and JupyterLab (with the extension jupytext) understand a common Python file with markers # %%: each marker opens a cell. The file remains a script that runs entirely, with the advantage of readable differences in Git, which the format .ipynb (a giant JSON with embedded outputs) does not provide:

    exemplos/celulas.py
    # %% [markdown]
    # # Notas de vendas
    # Cada bloco `# %%` é uma célula no VS Code e no JupyterLab (com jupytext).
    
    # %%
    import numpy as np
    
    vendas = np.array([120.0, 80.5, 99.9, 150.0])
    
    # %%
    print(vendas.mean().round(2))
    
    # %%
    print(vendas.max())
    

    Measuring time in the notebook

    In Jupyter, the "magic" %timeit runs the code multiple times and returns a reliable average, much better than a time.time() isolated. In a common script, the equivalent is the module timeit, which chapter 18 uses.