diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml
index 331971b..36055ee 100644
--- a/.github/workflows/quality-checks.yml
+++ b/.github/workflows/quality-checks.yml
@@ -32,6 +32,9 @@ jobs:
- name: Show Python version
run: python --version
+ - name: Install external-library dependencies
+ run: python -m pip install --disable-pip-version-check -r requirements-external.txt
+
- name: Compile Python files
run: python -m compileall -q -x '(^|/)\.git/' .
diff --git a/README.md b/README.md
index f267062..d22c4c3 100644
--- a/README.md
+++ b/README.md
@@ -74,9 +74,11 @@ Detailed explanations:
## Current status
-The project foundation and **Phases 1–8 are complete**. [Phase 8: Standard Library](standard-library/README.md) now contains nine reviewed chapters: [`pathlib`](standard-library/01-pathlib/README.md), [`datetime`](standard-library/02-datetime/README.md), [`json`](standard-library/03-json/README.md), [`csv`](standard-library/04-csv/README.md), [`logging`](standard-library/05-logging/README.md), [`collections`](standard-library/06-collections/README.md), [`itertools`](standard-library/07-itertools/README.md), [`decimal`](standard-library/08-decimal/README.md), and [`os` + `shutil`](standard-library/09-os-shutil/README.md).
+The project foundation and **Phases 1–8 are complete**. **Phase 9: External Libraries is now in progress** with its first reviewed-track chapter, [`pandas`](external-libraries/01-pandas/README.md), targeting pandas 3.0.x.
-Together, Phase 8 moves from path modeling through time, structured data, diagnostics, specialized containers, lazy iteration, decimal arithmetic, and finally operating-system/filesystem contracts for environment state, traversal, metadata, copy, move, removal, platform capabilities, and archive safety. Phase 9: External Libraries is the next planned curriculum phase. See the [roadmap](docs/roadmap.en.md) or the [full learning path](docs/learning-path.en.md) for direct chapter links and current status.
+The new phase adds an explicit third-party dependency contract through [`requirements-external.txt`](requirements-external.txt), and repository CI installs those dependencies before running approved examples. The pandas chapter covers labeled tabular data, indexes and alignment, dtypes, selection, pandas 3.0 Copy-on-Write, missing-data policy, vectorized transformations, grouping, validated joins, reshaping, and deterministic CSV pipelines.
+
+The next planned Phase 9 chapters are `openpyxl`, `requests`, and `pytest`. See the [External Libraries index](external-libraries/README.md), [roadmap](docs/roadmap.en.md), or [full learning path](docs/learning-path.en.md) for current status.
## Visual identity
diff --git a/docs/learning-path.en.md b/docs/learning-path.en.md
index b927223..d0e8f81 100644
--- a/docs/learning-path.en.md
+++ b/docs/learning-path.en.md
@@ -115,9 +115,16 @@ Phase 7 is complete with five reviewed chapters. Chapters 01–02 establish exce
Phase 8 is complete with nine reviewed chapters. The sequence moves from path modeling through time, structured data formats, runtime diagnostics, specialized containers, lazy iteration, decimal arithmetic, and finally operating-system and filesystem contracts. Chapter 09 closes the phase with environment state, path-like boundaries, deterministic scanning and traversal, metadata, copy/move/removal behavior, platform capabilities, and archive-safety decisions.
-## Phase 9 · External Libraries ⏳
+## Phase 9 · External Libraries 🚧
-Planned. Direct chapter links will appear here when the phase begins.
+[Open the External Libraries section index](../external-libraries/README.md)
+
+1. ✅ [`pandas`: Working with Tabular Data](../external-libraries/01-pandas/README.md)
+2. ⏳ `openpyxl`
+3. ⏳ `requests`
+4. ⏳ `pytest`
+
+Phase 9 is in progress. Chapter 01 establishes third-party dependency contracts and introduces labeled/tabular data with pandas 3.0.x: `Series`, `DataFrame`, indexes, label alignment, dtypes, selection, Copy-on-Write, missing data, vectorized transformations, grouping, validated joins, reshaping, and deterministic CSV pipelines.
## Phase 10 · Practical Projects ⏳
diff --git a/docs/learning-path.es.md b/docs/learning-path.es.md
index c783e10..f151572 100644
--- a/docs/learning-path.es.md
+++ b/docs/learning-path.es.md
@@ -115,9 +115,16 @@ La Fase 7 está completada con cinco capítulos revisados. Los Capítulos 01–0
La Fase 8 está completada con nueve capítulos revisados. La secuencia avanza desde modelado de rutas por tiempo, formatos estructurados de datos, diagnóstico en runtime, contenedores especializados, iteración lazy, aritmética decimal y finalmente contratos del sistema operativo y filesystem. El Capítulo 09 cierra la fase con estado del entorno, fronteras path-like, exploración y recorrido deterministas, metadatos, comportamiento de copia/movimiento/eliminación, capacidades de plataforma y decisiones de seguridad para archives.
-## Fase 9 · Bibliotecas Externas ⏳
+## Fase 9 · Bibliotecas Externas 🚧
-Planificado. Los enlaces directos a los capítulos aparecerán aquí cuando comience la fase.
+[Abre el índice de la sección Bibliotecas Externas](../external-libraries/README.es.md)
+
+1. ✅ [`pandas`: Trabajando con Datos Tabulares](../external-libraries/01-pandas/README.es.md)
+2. ⏳ `openpyxl`
+3. ⏳ `requests`
+4. ⏳ `pytest`
+
+La Fase 9 está en progreso. El Capítulo 01 establece contratos de dependencias de terceros e introduce datos etiquetados/tabulares con pandas 3.0.x: `Series`, `DataFrame`, índices, alineación por labels, dtypes, selección, Copy-on-Write, datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV deterministas.
## Fase 10 · Proyectos Prácticos ⏳
diff --git a/docs/learning-path.pt-BR.md b/docs/learning-path.pt-BR.md
index 6aa0a21..6e7f0cf 100644
--- a/docs/learning-path.pt-BR.md
+++ b/docs/learning-path.pt-BR.md
@@ -115,9 +115,16 @@ A Fase 7 está concluída com cinco capítulos revisados. Os Capítulos 01–02
A Fase 8 está concluída com nove capítulos revisados. A sequência avança de modelagem de caminhos por tempo, formatos estruturados de dados, diagnóstico em runtime, contêineres especializados, iteração lazy, aritmética decimal e finalmente contratos do sistema operacional e filesystem. O Capítulo 09 encerra a fase com estado do ambiente, fronteiras path-like, varredura e travessia determinísticas, metadados, comportamento de cópia/movimentação/remoção, capacidades de plataforma e decisões de segurança para archives.
-## Fase 9 · Bibliotecas Externas ⏳
+## Fase 9 · Bibliotecas Externas 🚧
-Planejado. Os links diretos dos capítulos aparecerão aqui quando a fase começar.
+[Abra o índice da seção Bibliotecas Externas](../external-libraries/README.pt-BR.md)
+
+1. ✅ [`pandas`: Trabalhando com Dados Tabulares](../external-libraries/01-pandas/README.pt-BR.md)
+2. ⏳ `openpyxl`
+3. ⏳ `requests`
+4. ⏳ `pytest`
+
+A Fase 9 está em andamento. O Capítulo 01 estabelece contratos de dependências de terceiros e introduz dados rotulados/tabulares com pandas 3.0.x: `Series`, `DataFrame`, índices, alinhamento por labels, dtypes, seleção, Copy-on-Write, dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV determinísticos.
## Fase 10 · Projetos Práticos ⏳
diff --git a/docs/localized/README.es.md b/docs/localized/README.es.md
index dace9bf..da97877 100644
--- a/docs/localized/README.es.md
+++ b/docs/localized/README.es.md
@@ -74,9 +74,11 @@ Explicaciones detalladas:
## Estado actual
-La base del proyecto y las **Fases 1–8 están completadas**. La [Fase 8: Biblioteca Estándar](../../standard-library/README.es.md) ahora reúne nueve capítulos revisados: [`pathlib`](../../standard-library/01-pathlib/README.es.md), [`datetime`](../../standard-library/02-datetime/README.es.md), [`json`](../../standard-library/03-json/README.es.md), [`csv`](../../standard-library/04-csv/README.es.md), [`logging`](../../standard-library/05-logging/README.es.md), [`collections`](../../standard-library/06-collections/README.es.md), [`itertools`](../../standard-library/07-itertools/README.es.md), [`decimal`](../../standard-library/08-decimal/README.es.md) y [`os` + `shutil`](../../standard-library/09-os-shutil/README.es.md).
+La base del proyecto y las **Fases 1–8 están completadas**. La **Fase 9: Bibliotecas Externas está ahora en progreso** con su primer capítulo de la ruta, [`pandas`](../../external-libraries/01-pandas/README.es.md), apuntando a pandas 3.0.x.
-En conjunto, los capítulos de la Fase 8 avanzan desde modelado de rutas por tiempo, datos estructurados, diagnóstico, contenedores especializados, iteración lazy, aritmética decimal y finalmente contratos de sistema operativo/filesystem para estado del entorno, recorrido, metadatos, copia, movimiento, eliminación, capacidades de plataforma y seguridad de archives. La Fase 9: Bibliotecas Externas es la siguiente fase planificada. Consulta el [roadmap](../roadmap.es.md) o la [ruta completa de aprendizaje](../learning-path.es.md) para enlaces directos y estado actualizado.
+La nueva fase añade un contrato explícito de dependencias de terceros mediante [`requirements-external.txt`](../../requirements-external.txt), y el CI del repositorio instala esas dependencias antes de ejecutar los ejemplos aprobados. El capítulo de pandas cubre datos tabulares etiquetados, índices y alineación, dtypes, selección, Copy-on-Write de pandas 3.0, política de datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV deterministas.
+
+Los siguientes capítulos planificados de la Fase 9 son `openpyxl`, `requests` y `pytest`. Consulta el [índice de Bibliotecas Externas](../../external-libraries/README.es.md), el [roadmap](../roadmap.es.md) o la [ruta completa de aprendizaje](../learning-path.es.md) para el estado actualizado.
## Identidad visual
diff --git a/docs/localized/README.pt-BR.md b/docs/localized/README.pt-BR.md
index 2749532..88bdcbf 100644
--- a/docs/localized/README.pt-BR.md
+++ b/docs/localized/README.pt-BR.md
@@ -74,9 +74,11 @@ Explicações detalhadas:
## Status atual
-A fundação do projeto e as **Fases 1–8 estão concluídas**. A [Fase 8: Biblioteca Padrão](../../standard-library/README.pt-BR.md) agora reúne nove capítulos revisados: [`pathlib`](../../standard-library/01-pathlib/README.pt-BR.md), [`datetime`](../../standard-library/02-datetime/README.pt-BR.md), [`json`](../../standard-library/03-json/README.pt-BR.md), [`csv`](../../standard-library/04-csv/README.pt-BR.md), [`logging`](../../standard-library/05-logging/README.pt-BR.md), [`collections`](../../standard-library/06-collections/README.pt-BR.md), [`itertools`](../../standard-library/07-itertools/README.pt-BR.md), [`decimal`](../../standard-library/08-decimal/README.pt-BR.md) e [`os` + `shutil`](../../standard-library/09-os-shutil/README.pt-BR.md).
+A fundação do projeto e as **Fases 1–8 estão concluídas**. A **Fase 9: Bibliotecas Externas agora está em andamento** com seu primeiro capítulo da trilha, [`pandas`](../../external-libraries/01-pandas/README.pt-BR.md), tendo pandas 3.0.x como alvo.
-Juntos, os capítulos da Fase 8 avançam de modelagem de caminhos por tempo, dados estruturados, diagnóstico, contêineres especializados, iteração lazy, aritmética decimal e finalmente contratos de sistema operacional/filesystem para estado do ambiente, travessia, metadados, cópia, movimentação, remoção, capacidades de plataforma e segurança de archives. A Fase 9: Bibliotecas Externas é a próxima fase planejada. Consulte o [roadmap](../roadmap.pt-BR.md) ou a [trilha completa de estudos](../learning-path.pt-BR.md) para links diretos e status atualizado.
+A nova fase acrescenta um contrato explícito de dependências de terceiros por meio de [`requirements-external.txt`](../../requirements-external.txt), e o CI do repositório instala essas dependências antes de executar os exemplos aprovados. O capítulo de pandas cobre dados tabulares rotulados, índices e alinhamento, dtypes, seleção, Copy-on-Write do pandas 3.0, política de dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV determinísticos.
+
+Os próximos capítulos planejados da Fase 9 são `openpyxl`, `requests` e `pytest`. Consulte o [índice de Bibliotecas Externas](../../external-libraries/README.pt-BR.md), o [roadmap](../roadmap.pt-BR.md) ou a [trilha completa de estudos](../learning-path.pt-BR.md) para o status atualizado.
## Identidade visual
diff --git a/docs/project-structure.en.md b/docs/project-structure.en.md
index b6c502d..a8bf9be 100644
--- a/docs/project-structure.en.md
+++ b/docs/project-structure.en.md
@@ -19,6 +19,7 @@ python-study-guide/
├── CONTRIBUTING.md
├── LICENSE
├── README.md
+├── requirements-external.txt
├── SECURITY.md
├── SUPPORT.md
├── assets/
@@ -189,6 +190,19 @@ python-study-guide/
│ └── formatting.py
├── exercises/
├── external-libraries/
+│ ├── README.md
+│ ├── README.pt-BR.md
+│ ├── README.es.md
+│ └── 01-pandas/
+│ ├── README.md
+│ ├── README.pt-BR.md
+│ ├── README.es.md
+│ └── examples/
+│ ├── csv_pipeline.py
+│ ├── dataframe_basics.py
+│ ├── filter_and_assign.py
+│ ├── groupby_summary.py
+│ └── merge_tables.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -524,7 +538,7 @@ python-study-guide/
- `docs/`: master learning paths, roadmaps, project architecture, localized project documents, policies, and responsible AI-assisted development guidance.
- `errors-files-and-modules/`: complete Phase 7 learning path. Chapters 01–05 cover runtime exception handling, deliberate raising and custom exceptions, safe text-file I/O with `open()` and `with`, TXT/CSV/JSON parsing and writing, and code organization through imports, modules, regular packages, execution context, and dependency design, in English, Brazilian Portuguese, and Spanish with deterministic executable examples.
- `exercises/`: focused practice activities connected to learning chapters.
-- `external-libraries/`: future guides to third-party packages.
+- `external-libraries/`: Phase 9 learning path for third-party packages. It currently contains the multilingual pandas 3.0.x chapter and five deterministic executable examples, with `openpyxl`, `requests`, and `pytest` planned next.
- `functions/`: complete Phase 5 learning path. Chapters 01–09 cover defining and calling functions, required inputs, returned values, scope and name lookup, type hints for function interfaces, default values including definition-time evaluation and mutable-default safety, variable-length positional and keyword argument collection with `*args` and `**kwargs`, composition through helper and coordinating functions with explicit dependencies and simple call graphs, and explicit data-flow tracing across calls including parameter bindings, rebinding versus mutation, `None`, tuple results, and return-based handoffs, in English, Brazilian Portuguese, and Spanish with deterministic executable examples.
- `fundamentals/`: complete Phase 1 learning path. Its six chapters teach how Python runs a program, how to use `print()` and `input()`, how assignment and naming work, how to recognize and inspect common built-in data types, and how to convert compatible values deliberately, with aligned multilingual explanations and executable examples.
- `practical-projects/`: future small projects combining several concepts.
diff --git a/docs/project-structure.es.md b/docs/project-structure.es.md
index c38f89d..24b205f 100644
--- a/docs/project-structure.es.md
+++ b/docs/project-structure.es.md
@@ -19,6 +19,7 @@ python-study-guide/
├── CONTRIBUTING.md
├── LICENSE
├── README.md
+├── requirements-external.txt
├── SECURITY.md
├── SUPPORT.md
├── assets/
@@ -189,6 +190,19 @@ python-study-guide/
│ └── formatting.py
├── exercises/
├── external-libraries/
+│ ├── README.md
+│ ├── README.pt-BR.md
+│ ├── README.es.md
+│ └── 01-pandas/
+│ ├── README.md
+│ ├── README.pt-BR.md
+│ ├── README.es.md
+│ └── examples/
+│ ├── csv_pipeline.py
+│ ├── dataframe_basics.py
+│ ├── filter_and_assign.py
+│ ├── groupby_summary.py
+│ └── merge_tables.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -524,7 +538,7 @@ python-study-guide/
- `docs/`: rutas completas de aprendizaje, roadmaps, arquitectura del proyecto, documentos localizados, políticas y guía de desarrollo responsable asistido por IA.
- `errors-files-and-modules/`: ruta completa de la Fase 7. Los Capítulos 01–05 cubren manejo de excepciones en runtime, lanzamiento deliberado y excepciones personalizadas, I/O seguro de archivos de texto con `open()` y `with`, parsing y escritura de TXT/CSV/JSON y organización del código mediante imports, módulos, paquetes regulares, contexto de ejecución y diseño de dependencias, en inglés, portugués de Brasil y español con ejemplos ejecutables deterministas.
- `exercises/`: actividades prácticas relacionadas con los capítulos.
-- `external-libraries/`: futuras guías sobre paquetes de terceros.
+- `external-libraries/`: ruta de la Fase 9 para paquetes de terceros. Actualmente contiene el capítulo multilingüe de pandas 3.0.x y cinco ejemplos ejecutables deterministas, con `openpyxl`, `requests` y `pytest` planificados a continuación.
- `functions/`: ruta completa de la Fase 5. Los Capítulos 01–09 cubren definición y llamada de funciones, entradas obligatorias, valores retornados, alcance y búsqueda de nombres, type hints para interfaces de funciones, valores predeterminados incluida la evaluación al definir la función y la seguridad con valores mutables, recolección de argumentos posicionales y por palabra clave de cantidad variable con `*args` y `**kwargs`, composición mediante funciones auxiliares y coordinadoras con dependencias explícitas y grafos simples de llamadas, y seguimiento explícito del flujo de datos entre llamadas, incluidos vínculos de parámetros, reasignación frente a mutación, `None`, resultados en tupla y traspasos mediante `return`, en inglés, portugués de Brasil y español, con ejemplos ejecutables determinísticos.
- `fundamentals/`: ruta completa de la Fase 1. Sus seis capítulos enseñan cómo Python ejecuta un programa, cómo usar `print()` e `input()`, cómo funcionan la asignación y los nombres, cómo reconocer e inspeccionar tipos de datos incorporados comunes y cómo convertir valores compatibles de forma deliberada, con explicaciones multilingües alineadas y ejemplos ejecutables.
- `practical-projects/`: futuros proyectos pequeños que combinarán varios conceptos.
diff --git a/docs/project-structure.pt-BR.md b/docs/project-structure.pt-BR.md
index 4ec7af6..b714643 100644
--- a/docs/project-structure.pt-BR.md
+++ b/docs/project-structure.pt-BR.md
@@ -19,6 +19,7 @@ python-study-guide/
├── CONTRIBUTING.md
├── LICENSE
├── README.md
+├── requirements-external.txt
├── SECURITY.md
├── SUPPORT.md
├── assets/
@@ -189,6 +190,19 @@ python-study-guide/
│ └── formatting.py
├── exercises/
├── external-libraries/
+│ ├── README.md
+│ ├── README.pt-BR.md
+│ ├── README.es.md
+│ └── 01-pandas/
+│ ├── README.md
+│ ├── README.pt-BR.md
+│ ├── README.es.md
+│ └── examples/
+│ ├── csv_pipeline.py
+│ ├── dataframe_basics.py
+│ ├── filter_and_assign.py
+│ ├── groupby_summary.py
+│ └── merge_tables.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -524,7 +538,7 @@ python-study-guide/
- `docs/`: trilhas completas de estudos, roadmaps, arquitetura do projeto, documentos localizados, políticas e guia de desenvolvimento responsável assistido por IA.
- `errors-files-and-modules/`: trilha completa da Fase 7. Os Capítulos 01–05 cobrem tratamento de exceções em runtime, levantamento deliberado e exceções personalizadas, I/O seguro de arquivos de texto com `open()` e `with`, parsing e escrita de TXT/CSV/JSON e organização do código por imports, módulos, pacotes regulares, contexto de execução e design de dependências, em inglês, português brasileiro e espanhol com exemplos executáveis determinísticos.
- `exercises/`: atividades práticas relacionadas aos capítulos.
-- `external-libraries/`: futuros guias sobre pacotes de terceiros.
+- `external-libraries/`: trilha da Fase 9 para pacotes de terceiros. Atualmente contém o capítulo multilíngue de pandas 3.0.x e cinco exemplos executáveis determinísticos, com `openpyxl`, `requests` e `pytest` planejados na sequência.
- `functions/`: trilha completa da Fase 5. Os Capítulos 01–09 cobrem definição e chamada de funções, entradas obrigatórias, valores retornados, escopo e busca de nomes, type hints para interfaces de funções, valores padrão incluindo avaliação no momento da definição e segurança com padrões mutáveis, coleta de argumentos posicionais e nomeados de quantidade variável com `*args` e `**kwargs`, composição por funções auxiliares e coordenadoras com dependências explícitas e grafos simples de chamadas e rastreamento explícito do fluxo de dados entre chamadas, incluindo vínculos de parâmetros, reatribuição versus mutação, `None`, resultados em tupla e passagens por `return`, em inglês, português brasileiro e espanhol com exemplos executáveis determinísticos.
- `fundamentals/`: trilha completa da Fase 1. Seus seis capítulos ensinam como o Python executa um programa, como usar `print()` e `input()`, como funcionam atribuição e nomes, como reconhecer e inspecionar tipos de dados embutidos comuns e como converter valores compatíveis de forma deliberada, com explicações multilíngues alinhadas e exemplos executáveis.
- `practical-projects/`: futuros projetos pequenos combinando diversos conceitos.
diff --git a/docs/roadmap.en.md b/docs/roadmap.en.md
index b30f73f..ad88bbc 100644
--- a/docs/roadmap.en.md
+++ b/docs/roadmap.en.md
@@ -23,10 +23,10 @@ This roadmap tracks both the educational curriculum and the repository foundatio
| 6. Comments, documentation, and clean code | Complete | Six reviewed chapters are available and the pilot educational section is officially complete |
| 7. Errors, files, and modules | Complete | Five reviewed chapters cover exception handling, deliberate exception signaling, safe file I/O, TXT/CSV/JSON data formats, and imports/modules/packages |
| 8. Standard library | Complete | Nine reviewed chapters cover paths, date/time, JSON, CSV, logging, specialized collections, lazy iteration, decimal arithmetic, and OS/filesystem operations |
-| 9. External libraries | Planned | Curriculum not started |
+| 9. External libraries | In progress | pandas 3.0.x chapter available with an explicit third-party dependency contract and deterministic examples |
| 10. Practical projects | Planned | Curriculum not started |
-Phases 0–8 are complete. Phase 8 now provides nine reviewed Standard Library chapters, ending with `os` and `shutil` contracts for environment state, filesystem traversal, metadata, copying, moving, recursive removal, platform capabilities, and archive safety. Phase 9: External Libraries is the next planned curriculum phase.
+Phases 0–8 are complete. Phase 9: External Libraries is now in progress with `pandas` as its first chapter. The phase introduces explicit third-party dependency contracts through `requirements-external.txt`, while `openpyxl`, `requests`, and `pytest` remain planned.
## Phase 0: Project foundation
@@ -154,10 +154,14 @@ Phase 8 is complete. Chapters 01–08 build contracts for paths, date/time, stru
## Phase 9: External libraries
-- `pandas`
-- `openpyxl`
-- `requests`
-- `pytest`
+See the [section learning path](../external-libraries/README.md).
+
+- [x] [`pandas`](../external-libraries/01-pandas/README.md)
+- [ ] `openpyxl`
+- [ ] `requests`
+- [ ] `pytest`
+
+Phase 9 is in progress. Chapter 01 introduces pandas 3.0.x for labeled tabular data, selection, Copy-on-Write, missing-data policy, vectorized transformations, grouping, validated joins, reshaping, and CSV pipelines. Executable external-library examples use the dependency contract declared in [`requirements-external.txt`](../requirements-external.txt).
## Phase 10: Practical projects
diff --git a/docs/roadmap.es.md b/docs/roadmap.es.md
index 0b98864..a21dd25 100644
--- a/docs/roadmap.es.md
+++ b/docs/roadmap.es.md
@@ -23,10 +23,10 @@ Este roadmap acompaña tanto la ruta educativa como la base del repositorio que
| 6. Comentarios, documentación y código limpio | Completada | Seis capítulos revisados están disponibles y la sección educativa piloto está oficialmente completada |
| 7. Errores, archivos y módulos | Completada | Cinco capítulos revisados cubren manejo de excepciones, señalización deliberada, I/O seguro de archivos, formatos TXT/CSV/JSON e imports/módulos/paquetes |
| 8. Biblioteca estándar | Completada | Nueve capítulos revisados cubren rutas, fecha/hora, JSON, CSV, logging, colecciones especializadas, iteración lazy, aritmética decimal y operaciones de OS/filesystem |
-| 9. Bibliotecas externas | Planificada | Contenido todavía no iniciado |
+| 9. Bibliotecas externas | En progreso | Capítulo de pandas 3.0.x disponible con contrato explícito de dependencias de terceros y ejemplos deterministas |
| 10. Proyectos prácticos | Planificada | Contenido todavía no iniciado |
-Las Fases 0–8 están completadas. La Fase 8 ahora ofrece nueve capítulos revisados de la Biblioteca Estándar, cerrando con contratos de `os` y `shutil` para estado del entorno, recorrido del filesystem, metadatos, copia, movimiento, eliminación recursiva, capacidades de plataforma y seguridad de archives. La Fase 9: Bibliotecas Externas es la siguiente fase planificada del currículo.
+Las Fases 0–8 están completadas. La Fase 9: Bibliotecas Externas está ahora en progreso con `pandas` como primer capítulo. La fase introduce contratos explícitos de dependencias de terceros mediante `requirements-external.txt`, mientras `openpyxl`, `requests` y `pytest` siguen planificados.
## Fase 0: Base del proyecto
@@ -154,10 +154,14 @@ La Fase 8 está completada. Los Capítulos 01–08 construyen contratos para rut
## Fase 9: Bibliotecas externas
-- `pandas`
-- `openpyxl`
-- `requests`
-- `pytest`
+Consulta la [ruta de aprendizaje de la sección](../external-libraries/README.es.md).
+
+- [x] [`pandas`](../external-libraries/01-pandas/README.es.md)
+- [ ] `openpyxl`
+- [ ] `requests`
+- [ ] `pytest`
+
+La Fase 9 está en progreso. El Capítulo 01 introduce pandas 3.0.x para datos tabulares etiquetados, selección, Copy-on-Write, política de datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV. Los ejemplos ejecutables de bibliotecas externas usan el contrato de dependencias declarado en [`requirements-external.txt`](../requirements-external.txt).
## Fase 10: Proyectos prácticos
diff --git a/docs/roadmap.pt-BR.md b/docs/roadmap.pt-BR.md
index 8973fa3..f35c799 100644
--- a/docs/roadmap.pt-BR.md
+++ b/docs/roadmap.pt-BR.md
@@ -23,10 +23,10 @@ Este roadmap acompanha tanto a trilha educacional quanto a fundação do reposit
| 6. Comentários, documentação e código limpo | Concluída | Seis capítulos revisados estão disponíveis e a seção educacional-piloto está oficialmente concluída |
| 7. Erros, arquivos e módulos | Concluída | Cinco capítulos revisados cobrem tratamento de exceções, sinalização deliberada, I/O seguro de arquivos, formatos TXT/CSV/JSON e imports/módulos/pacotes |
| 8. Biblioteca padrão | Concluída | Nove capítulos revisados cobrem caminhos, data/hora, JSON, CSV, logging, coleções especializadas, iteração lazy, aritmética decimal e operações de OS/filesystem |
-| 9. Bibliotecas externas | Planejada | Conteúdo ainda não iniciado |
+| 9. Bibliotecas externas | Em andamento | Capítulo de pandas 3.0.x disponível com contrato explícito de dependências de terceiros e exemplos determinísticos |
| 10. Projetos práticos | Planejada | Conteúdo ainda não iniciado |
-As Fases 0–8 estão concluídas. A Fase 8 agora oferece nove capítulos revisados da Biblioteca Padrão, encerrando com contratos de `os` e `shutil` para estado do ambiente, travessia do filesystem, metadados, cópia, movimentação, remoção recursiva, capacidades da plataforma e segurança de archives. A Fase 9: Bibliotecas Externas é a próxima fase planejada do currículo.
+As Fases 0–8 estão concluídas. A Fase 9: Bibliotecas Externas agora está em andamento com `pandas` como primeiro capítulo. A fase introduz contratos explícitos de dependências de terceiros por meio de `requirements-external.txt`, enquanto `openpyxl`, `requests` e `pytest` permanecem planejados.
## Fase 0: Fundação do projeto
@@ -154,10 +154,14 @@ A Fase 8 está concluída. Os Capítulos 01–08 constroem contratos para caminh
## Fase 9: Bibliotecas externas
-- `pandas`
-- `openpyxl`
-- `requests`
-- `pytest`
+Veja a [trilha de aprendizagem da seção](../external-libraries/README.pt-BR.md).
+
+- [x] [`pandas`](../external-libraries/01-pandas/README.pt-BR.md)
+- [ ] `openpyxl`
+- [ ] `requests`
+- [ ] `pytest`
+
+A Fase 9 está em andamento. O Capítulo 01 introduz pandas 3.0.x para dados tabulares rotulados, seleção, Copy-on-Write, política de dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV. Os exemplos executáveis de bibliotecas externas usam o contrato de dependências declarado em [`requirements-external.txt`](../requirements-external.txt).
## Fase 10: Projetos práticos
diff --git a/external-libraries/01-pandas/README.es.md b/external-libraries/01-pandas/README.es.md
new file mode 100644
index 0000000..e5752cb
--- /dev/null
+++ b/external-libraries/01-pandas/README.es.md
@@ -0,0 +1,1374 @@
+
+
+# Trabajando con Datos Tabulares Usando `pandas`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Volver a Bibliotecas Externas](../README.es.md) · [← Fase anterior: `os` + `shutil`](../../standard-library/09-os-shutil/README.es.md)
+
+La Fase 9 comienza donde termina la biblioteca estándar: incorporando paquetes de terceros con sus propios ciclos de lanzamiento, contratos de dependencias y abstracciones específicas de dominio.
+
+`pandas` es la primera biblioteca externa porque se conecta directamente con conceptos ya estudiados: listas, diccionarios, CSV, JSON, fechas, archivos, funciones, excepciones, rutas y validación de datos. El nuevo desafío no es solo aprender métodos. Es aprender a preservar la **semántica de la tabla** mientras las transformaciones se vuelven más expresivas.
+
+Este capítulo apunta a **pandas 3.0.x** y fue investigado usando la documentación oficial de pandas **3.0.5**. pandas 3.0 es compatible con Python 3.11 o superior.
+
+**Tiempo estimado de estudio:** 240–330 minutos.
+
+## Objetivos de aprendizaje
+
+Al final de este capítulo deberías poder:
+
+- explicar cuándo pandas es más apropiado que las colecciones incorporadas;
+- crear e inspeccionar objetos `Series` y `DataFrame`;
+- razonar sobre índices, labels, alineación, columnas y dtypes;
+- seleccionar filas y columnas con corchetes, `.loc` y `.iloc`;
+- construir máscaras booleanas y actualizar filas de forma segura;
+- comprender Copy-on-Write en pandas 3.0 y por qué chained assignment no es válido;
+- tratar valores ausentes con una política explícita;
+- convertir columnas numéricas, de texto y datetime deliberadamente;
+- agregar con `groupby()`, `agg()` y `transform()`;
+- combinar tablas con `merge()` validado y `concat()`;
+- remodelar datos con `pivot_table()` y `melt()`;
+- cargar y guardar CSV con decisiones explícitas de schema;
+- preferir operaciones vectorizadas cuando expresan el problema;
+- reconocer cuándo `apply()` y la iteración por filas no son buenos valores predeterminados;
+- construir pipelines de datos tabulares deterministas y revisables.
+
+## 1. Por qué existe `pandas`
+
+`pandas` es una biblioteca de terceros para datos etiquetados y tabulares. Es especialmente útil cuando los datos tienen filas, columnas, etiquetas, valores ausentes, tipos diferentes por columna o requieren filtrado, agrupación, joins, reshape y entrada/salida mediante archivos.
+
+No sustituye las colecciones de Python. Una lista o un diccionario suele ser mejor para pequeños estados de aplicación. `pandas` resulta atractivo cuando el problema es principalmente una tabla y las operaciones se aplican a columnas o grupos de filas.
+
+## 2. Las bibliotecas externas introducen contratos de dependencias
+
+A diferencia de la biblioteca estándar, pandas debe instalarse en el entorno Python que ejecutará el código. El repositorio declara las dependencias ejecutables de la Fase 9 en `requirements-external.txt`.
+
+Un contrato de dependencias responde preguntas como:
+
+```text
+¿Qué paquete se requiere?
+¿Qué versiones admite el capítulo?
+¿Qué versiones de Python admite ese paquete?
+¿Cómo reproduce CI el mismo entorno?
+¿Qué comportamientos cambiaron entre versiones principales?
+```
+
+Este capítulo apunta deliberadamente a pandas 3.0.x en lugar de fingir que todas las versiones históricas se comportan igual.
+
+## 3. Instala pandas en un entorno aislado
+
+Un entorno virtual mantiene las dependencias del proyecto separadas de otras instalaciones de Python.
+
+```bash
+python -m venv .venv
+```
+
+Actívalo según tu sistema operativo e instala el contrato de dependencias del repositorio:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+La documentación oficial también permite instalación directa con `pip install pandas` y mediante conda-forge. Aquí se prefiere un archivo de dependencias porque hace reproducible el contrato ejecutable de la guía.
+
+## 4. Importa pandas con el alias convencional
+
+La documentación y la comunidad de pandas usan `pd`:
+
+```python
+import pandas as pd
+```
+
+Seguir esta convención facilita comparar ejemplos con la documentación oficial y otros proyectos.
+
+## 5. `Series` modela una dimensión etiquetada
+
+Una `Series` es una estructura de datos unidimensional etiquetada. Combina valores con un índice. Una columna de DataFrame suele exponerse como una `Series`.
+
+```python
+import pandas as pd
+
+
+scores = pd.Series([8.5, 9.0, 7.5], index=["A", "B", "C"])
+print(scores.loc["B"])
+```
+
+```text
+9.0
+```
+
+Una `Series` no es simplemente una lista con más métodos. Las etiquetas participan en selección y alineación.
+
+## 6. `DataFrame` modela una tabla etiquetada
+
+Un `DataFrame` es una tabla bidimensional con filas y columnas etiquetadas. Columnas diferentes pueden tener dtypes diferentes, lo que lo hace apropiado para muchos datasets similares a hojas de cálculo, SQL y CSV.
+
+```python
+import pandas as pd
+
+
+people = pd.DataFrame(
+ {
+ "name": ["Ana", "Bruno"],
+ "age": [28, 34],
+ "active": [True, False],
+ }
+)
+print(people.shape)
+```
+
+```text
+(2, 3)
+```
+
+Un diccionario de secuencias con igual longitud es uno de los constructores más claros para ejemplos pequeños. Las claves se convierten en etiquetas de columnas.
+
+## 7. El índice forma parte del modelo de datos
+
+El índice etiqueta filas. El `RangeIndex` predeterminado es suficiente en muchos casos. Usa un índice personalizado significativo solo cuando las etiquetas de filas participen realmente en selección, alineación o identidad.
+
+```python
+import pandas as pd
+
+
+temperatures = pd.Series([21.5, 19.0], index=["morning", "evening"])
+print(temperatures.index.tolist())
+```
+
+```text
+['morning', 'evening']
+```
+
+No conviertas automáticamente todo identificador de negocio en índice. Una columna normal suele ser más fácil de validar, combinar, exportar y explicar.
+
+## 8. La alineación por etiquetas es poderosa y puede sorprender
+
+Cuando pandas combina objetos etiquetados, normalmente alinea valores por etiquetas del índice en lugar de hacerlo ciegamente por posición física.
+
+```python
+import pandas as pd
+
+
+left = pd.Series([10, 20], index=["a", "b"])
+right = pd.Series([1, 2], index=["b", "c"])
+print((left + right).to_dict())
+```
+
+La etiqueta compartida `b` recibe un valor de ambos objetos. Las etiquetas presentes solo en un lado quedan ausentes en el resultado.
+
+Trata el índice como dato, no decoración. Etiquetas inesperadas pueden cambiar aritmética, joins, asignaciones y comparaciones.
+
+## 9. Inspecciona columnas y dtypes temprano
+
+Un flujo confiable inspecciona lo cargado antes de transformarlo. `columns` muestra etiquetas y `dtypes` el dtype elegido para cada columna.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"label": ["x", "y"], "count": [1, 2]})
+print(table.columns.tolist())
+print(table.dtypes.astype(str).to_dict())
+```
+
+pandas 3.0 cambió un valor predeterminado importante: las columnas que contienen solo strings se infieren con el dtype dedicado `str` en lugar del histórico dtype genérico `object`.
+
+Esa es una razón para declarar explícitamente la versión de pandas de este capítulo.
+
+## 10. `shape`, `size` y `ndim` responden preguntas distintas
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+print(table.shape, table.size, table.ndim)
+```
+
+```text
+(3, 2) 6 2
+```
+
+- `shape` devuelve `(filas, columnas)`;
+- `size` devuelve la cantidad de celdas;
+- `ndim` devuelve el número de dimensiones.
+
+Son hechos estructurales, no validaciones por sí mismos.
+
+## 11. Previsualizar ayuda, pero no valida
+
+`head()` y `tail()` son herramientas rápidas de inspección. `sample()` puede revelar patrones fuera de las primeras filas, pero usa `random_state` cuando importa la reproducibilidad.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [10, 20, 30, 40]})
+print(table.sample(2, random_state=7)["value"].tolist())
+```
+
+Una vista previa no demuestra que existan columnas requeridas, que los dtypes sean correctos, que los identificadores sean únicos o que los valores estén dentro de rangos permitidos.
+
+## 12. `info()` y `describe()` responden preguntas distintas
+
+`DataFrame.info()` resume filas, nombres de columnas, conteos no nulos, dtypes y memoria aproximada. Es útil para inspección humana.
+
+`describe()` resume estadísticas como count, mean, dispersión y extremos para columnas apropiadas.
+
+```python
+import pandas as pd
+
+
+values = pd.DataFrame({"amount": [10.0, 20.0, 30.0]})
+print(values["amount"].describe()[["count", "mean", "max"]].to_dict())
+```
+
+Ninguna función comprende el significado de negocio. Un importe negativo puede ser matemáticamente válido pero inválido para un dataset. Un identificador puede parecer numérico sin tener sentido promediarlo.
+
+## 13. Selecciona una columna con corchetes
+
+`df["column"]` devuelve una `Series`.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"unit price": [10.0, 12.5]})
+prices = table["unit price"]
+print(type(prices).__name__)
+```
+
+```text
+Series
+```
+
+Prefiere corchetes al acceso por atributo como `df.column`. Los nombres pueden contener espacios, colisionar con atributos de DataFrame o elegirse dinámicamente.
+
+## 14. Selecciona varias columnas con una lista
+
+Pasar una lista de etiquetas devuelve un DataFrame y conserva el orden solicitado.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"a": [1], "b": [2], "c": [3]})
+subset = table[["c", "a"]]
+print(subset.columns.tolist())
+```
+
+```text
+['c', 'a']
+```
+
+La distinción importa: una string selecciona una columna como `Series`; una lista de strings selecciona una tabla `DataFrame`.
+
+## 15. Usa `.loc` para selección basada en etiquetas
+
+`.loc` selecciona por etiquetas y condiciones booleanas.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"status": ["new", "done"], "value": [5, 8]}, index=["a", "b"])
+print(table.loc["b", "value"])
+```
+
+```text
+8
+```
+
+`.loc` también es la herramienta preferida para asignación condicional porque filas objetivo y columna destino pueden expresarse en una operación.
+
+## 16. Usa `.iloc` para selección posicional
+
+`.iloc` selecciona por posición entera, independientemente de las etiquetas del índice.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"name": ["first", "second", "third"]}, index=[10, 20, 30])
+print(table.iloc[1, 0])
+```
+
+```text
+second
+```
+
+Usa `.iloc` cuando la posición misma sea significativa. No la uses solo porque la selección por etiquetas resulte menos familiar.
+
+## 17. Los slices por etiqueta y por posición tienen límites diferentes
+
+Con `.loc`, un slice por etiqueta incluye la etiqueta final cuando existe. Con `.iloc`, el slicing sigue Python posicional y excluye la posición final.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [10, 20, 30]}, index=["a", "b", "c"])
+print(table.loc["a":"b", "value"].tolist())
+print(table.iloc[0:2, 0].tolist())
+```
+
+```text
+[10, 20]
+[10, 20]
+```
+
+Los dos ejemplos devuelven los mismos valores por razones diferentes. Mantén separados los modelos mentales.
+
+## 18. Las máscaras booleanas filtran filas
+
+Una comparación contra una `Series` produce una `Series` booleana. Usar esa máscara con `.loc` conserva las filas donde la condición es verdadera.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"amount": [50, 120, 80]})
+mask = orders["amount"] >= 80
+print(orders.loc[mask, "amount"].tolist())
+```
+
+```text
+[120, 80]
+```
+
+Las máscaras son uno de los puentes principales entre lógica booleana de Python y operaciones orientadas a tablas.
+
+## 19. Combina máscaras con `&`, `|` y `~`
+
+Usa operadores booleanos elemento a elemento para condiciones de `Series` y pon cada comparación entre paréntesis.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {"status": ["paid", "paid", "pending"], "amount": [50, 150, 200]}
+)
+mask = (orders["status"] == "paid") & (orders["amount"] >= 100)
+print(orders.loc[mask, "amount"].tolist())
+```
+
+```text
+[150]
+```
+
+Los operadores escalares `and` y `or` no expresan lógica fila a fila para una `Series` de pandas.
+
+## 20. Asigna sobre el objeto que quieres cambiar
+
+Al actualizar un DataFrame, expresa el selector de filas y la columna destino en una sola operación `.loc`.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"amount": [50, 150], "priority": ["normal", "normal"]})
+orders.loc[orders["amount"] >= 100, "priority"] = "high"
+print(orders["priority"].tolist())
+```
+
+```text
+['normal', 'high']
+```
+
+Este patrón es explícito y compatible con Copy-on-Write de pandas 3.0.
+
+## 21. Copy-on-Write es la regla de pandas 3.0
+
+En pandas 3.0, los objetos derivados mediante indexación o métodos se comportan como copias desde el punto de vista del usuario. Modificar un objeto derivado no modifica el original.
+
+```python
+import pandas as pd
+
+
+original = pd.DataFrame({"value": [1, 2, 3]})
+subset = original["value"]
+subset.iloc[0] = 99
+
+print(original["value"].tolist())
+print(subset.tolist())
+```
+
+```text
+[1, 2, 3]
+[99, 2, 3]
+```
+
+Internamente pandas puede compartir memoria hasta que una escritura exija copiar. El contrato importante para código de aplicación es el comportamiento observable.
+
+## 22. Chained assignment no es una estrategia válida
+
+Código como este usa varios pasos de indexación:
+
+```text
+df["value"][mask] = 10
+```
+
+En pandas 3.0, chained assignment no actualiza el DataFrame original. La antigua ambigüedad que producía `SettingWithCopyWarning` fue sustituida por una regla más simple: modifica el objeto mismo en una operación.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [1, 2, 3]})
+table.loc[table["value"] >= 2, "value"] = 10
+print(table["value"].tolist())
+```
+
+```text
+[1, 10, 10]
+```
+
+Es un punto importante al consultar material antiguo de pandas 1.x o 2.x.
+
+## 23. Crea columnas derivadas con expresiones vectorizadas
+
+Las expresiones de columna operan sobre objetos `Series` completos y suelen ser más claras que un loop Python por cada fila.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"units": [2, 3], "unit_price": [10.0, 12.5]})
+sales["total"] = sales["units"] * sales["unit_price"]
+print(sales["total"].tolist())
+```
+
+```text
+[20.0, 37.5]
+```
+
+Es un hábito central de pandas: expresa la transformación en términos de columnas cuando la regla es columnar.
+
+## 24. `assign()` es útil en method chains
+
+`assign()` devuelve un DataFrame con columnas añadidas o reemplazadas.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"units": [2, 3], "price": [5.0, 8.0]})
+result = sales.assign(total=lambda frame: frame["units"] * frame["price"])
+print(result["total"].tolist())
+```
+
+```text
+[10.0, 24.0]
+```
+
+Úsalo cuando un pipeline sea más legible encadenando transformaciones. La asignación directa sigue siendo válida cuando es más clara.
+
+## 25. Renombra, elimina y ordena con intención
+
+`rename()` puede normalizar nombres externos incómodos. `drop()` elimina filas o columnas. `sort_values()` y `sort_index()` hacen explícito el orden.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"Order Amount": [20, 10], "temporary_note": ["b", "a"]})
+clean = (
+ table.rename(columns={"Order Amount": "amount"})
+ .drop(columns=["temporary_note"])
+ .sort_values("amount")
+)
+print(clean["amount"].tolist())
+```
+
+```text
+[10, 20]
+```
+
+Un campo eliminado puede ser imposible de reconstruir. Un sort puede ser necesario para reportes deterministas. Estas operaciones codifican política, no solo formato.
+
+## 26. Los datos ausentes necesitan una política explícita
+
+Los valores ausentes pueden significar desconocido, no aplicable, no recolectado, inválido, retrasado o intencionalmente vacío. No son equivalentes.
+
+Antes de llamar `dropna()` o `fillna()`, decide qué significa la ausencia para cada columna relevante.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [1.0, None, 3.0], "label": ["a", "b", None]})
+print(table.isna().sum().to_dict())
+```
+
+```text
+{'value': 1, 'label': 1}
+```
+
+Contar ausencias es observación. Eliminarlas o rellenarlas es una transformación que necesita una regla.
+
+## 27. `dropna()` descarta observaciones
+
+`dropna()` es correcto solo cuando las observaciones afectadas son realmente descartables bajo el contrato de datos.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"id": [1, 2, 3], "amount": [10.0, None, 30.0]})
+complete = table.dropna(subset=["amount"])
+print(complete["id"].tolist())
+```
+
+```text
+[1, 3]
+```
+
+Usar `dropna()` sin `subset` puede eliminar filas por campos irrelevantes para la operación actual.
+
+## 28. `fillna()` inserta un significado elegido
+
+Reemplazar un importe desconocido por cero afirma que cero es la interpretación correcta.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"discount": [0.1, None, 0.2]})
+filled = table["discount"].fillna(0.0)
+print(filled.tolist())
+```
+
+```text
+[0.1, 0.0, 0.2]
+```
+
+Documenta reglas de relleno porque cambian el dataset, no solo su apariencia.
+
+## 29. Los dtypes forman parte del schema
+
+Una columna que parece numérica puede haberse cargado como texto. Una fecha puede seguir siendo string. Un identificador puede necesitar permanecer textual aunque todos sus valores sean dígitos.
+
+Usa `astype()` cuando los valores ya sean válidos para el dtype destino:
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"units": ["1", "2", "3"]})
+table["units"] = table["units"].astype("int64")
+print(table["units"].sum())
+```
+
+```text
+6
+```
+
+Elige tipos según significado y operaciones, no solo apariencia.
+
+## 30. `to_numeric()` hace explícita la política de parsing
+
+`pd.to_numeric()` sirve cuando el parsing numérico puede fallar.
+
+```python
+import pandas as pd
+
+
+raw = pd.Series(["10", "invalid", "30"])
+parsed = pd.to_numeric(raw, errors="coerce")
+print(parsed.isna().sum())
+```
+
+```text
+1
+```
+
+`errors="coerce"` convierte entradas inválidas en valores ausentes. Solo es seguro si después el flujo audita y trata esas nuevas ausencias.
+
+## 31. Las operaciones de string son vectorizadas bajo `.str`
+
+El accessor `.str` aplica operaciones de string a una `Series`.
+
+```python
+import pandas as pd
+
+
+names = pd.Series([" Alpha ", "BETA "])
+normalized = names.str.strip().str.lower()
+print(normalized.tolist())
+```
+
+```text
+['alpha', 'beta']
+```
+
+Normaliza texto solo cuando coincida con el contrato del dominio. Pasar identificadores a minúsculas o descartar espacios puede cambiar significado.
+
+## 32. Convierte datetimes antes de usar semántica temporal
+
+Usa `pd.to_datetime()` cuando el texto debe convertirse en datetime real.
+
+```python
+import pandas as pd
+
+
+dates = pd.to_datetime(pd.Series(["2026-08-01", "2026-08-03"]), format="%Y-%m-%d")
+print((dates.iloc[1] - dates.iloc[0]).days)
+```
+
+```text
+2
+```
+
+El accessor `.dt` expone componentes vectorizados:
+
+```python
+import pandas as pd
+
+
+dates = pd.to_datetime(pd.Series(["2026-01-15", "2026-02-20"]))
+print(dates.dt.month.tolist())
+```
+
+```text
+[1, 2]
+```
+
+Los formatos ambiguos deben controlarse explícitamente en lugar de adivinarse.
+
+## 33. Los duplicados necesitan una definición
+
+Dos filas son duplicadas solo con respecto a columnas elegidas. `duplicated()` y `drop_duplicates()` aceptan `subset` para expresar la clave real de unicidad.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame(
+ {"id": [1, 1, 2], "note": ["first", "repeated", "other"]}
+)
+print(table.duplicated(subset=["id"]).tolist())
+```
+
+```text
+[False, True, False]
+```
+
+No dedupliques filas completas cuando la regla real es unicidad por identificador.
+
+## 34. Los métodos de frecuencia y resumen son diagnósticos compactos
+
+`value_counts()` muestra frecuencia de categorías. `nunique()` cuenta valores distintos no ausentes por defecto. Reducciones como `sum()`, `mean()`, `min()`, `max()` y `count()` resumen columnas.
+
+```python
+import pandas as pd
+
+
+statuses = pd.Series(["paid", "pending", "paid", "paid"])
+print(statuses.value_counts().sort_index().to_dict())
+```
+
+```text
+{'paid': 3, 'pending': 1}
+```
+
+Una frecuencia es evidencia sobre el dataset observado, no prueba de que toda categoría observada esté permitida.
+
+## 35. `groupby()` implementa split-apply-combine
+
+`groupby()` divide filas por una o más claves, aplica agregación o transformación y combina resultados.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {"category": ["A", "B", "A"], "amount": [10, 20, 30]}
+)
+summary = sales.groupby("category")["amount"].sum()
+print(summary.to_dict())
+```
+
+```text
+{'A': 40, 'B': 20}
+```
+
+Agrupar es central en pandas porque muchas preguntas analíticas son "calcula algo por categoría, cliente, fecha, región u otra clave".
+
+## 36. La agregación nombrada hace explícito el schema de salida
+
+Named aggregation permite declarar columna de origen y operación.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {"category": ["A", "A", "B"], "amount": [10.0, 30.0, 20.0]}
+)
+summary = sales.groupby("category", as_index=False).agg(
+ total=("amount", "sum"),
+ average=("amount", "mean"),
+)
+print(summary.to_dict(orient="records"))
+```
+
+```text
+[{'category': 'A', 'total': 40.0, 'average': 20.0}, {'category': 'B', 'total': 20.0, 'average': 20.0}]
+```
+
+Un schema de salida estable facilita validación, exportación y pruebas posteriores.
+
+## 37. `transform()` mantiene resultados alineados con las filas originales
+
+A diferencia de una agregación normal, `transform()` devuelve un resultado alineado con el índice original.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"team": ["A", "A", "B"], "score": [10, 20, 30]})
+sales["team_total"] = sales.groupby("team")["score"].transform("sum")
+print(sales["team_total"].tolist())
+```
+
+```text
+[30, 30, 30]
+```
+
+Es útil cuando una estadística del grupo debe quedar junto a cada observación.
+
+## 38. `merge()` combina tablas por claves
+
+`merge()` es la operación de join estilo base de datos de pandas.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"customer_id": [1, 2], "amount": [10, 20]})
+customers = pd.DataFrame({"customer_id": [1, 2], "name": ["A", "B"]})
+result = orders.merge(customers, on="customer_id", how="left")
+print(result["name"].tolist())
+```
+
+```text
+['A', 'B']
+```
+
+Un merge que termina sin error todavía puede ser incorrecto lógicamente si las claves tienen duplicados inesperados.
+
+## 39. Valida la cardinalidad del merge
+
+El argumento `validate` puede afirmar relaciones como `one_to_one`, `one_to_many`, `many_to_one` o `many_to_many`.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"customer_id": [1, 1], "amount": [10, 20]})
+customers = pd.DataFrame({"customer_id": [1], "name": ["A"]})
+result = orders.merge(
+ customers,
+ on="customer_id",
+ how="left",
+ validate="many_to_one",
+)
+print(len(result))
+```
+
+```text
+2
+```
+
+Cuando la cardinalidad forma parte del contrato, validarla convierte duplicaciones accidentales en fallos visibles en vez de multiplicar filas silenciosamente.
+
+## 40. `concat()` apila objetos compatibles
+
+`pd.concat()` combina objetos pandas a lo largo de un eje. Concatenar filas es común cuando varios archivos comparten schema.
+
+```python
+import pandas as pd
+
+
+first = pd.DataFrame({"id": [1, 2]})
+second = pd.DataFrame({"id": [3]})
+combined = pd.concat([first, second], ignore_index=True)
+print(combined["id"].tolist())
+```
+
+```text
+[1, 2, 3]
+```
+
+Después de concatenar, decide si deben conservarse o reiniciarse las etiquetas del índice original.
+
+## 41. `pivot_table()` resume en una matriz
+
+Una pivot table agrupa datos entre dimensiones de filas y columnas y agrega valores.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {
+ "region": ["north", "north", "south"],
+ "product": ["A", "B", "A"],
+ "amount": [10, 20, 30],
+ }
+)
+pivot = sales.pivot_table(
+ index="region",
+ columns="product",
+ values="amount",
+ aggfunc="sum",
+ fill_value=0,
+)
+print(pivot.loc["north", "B"])
+```
+
+```text
+20
+```
+
+Usa pivot table cuando la salida deseada sea una matriz de resumen.
+
+## 42. `melt()` convierte datos wide a long
+
+Los datos long suelen facilitar agrupación y visualización.
+
+```python
+import pandas as pd
+
+
+wide = pd.DataFrame({"item": ["A"], "jan": [10], "feb": [20]})
+long = wide.melt(id_vars="item", var_name="month", value_name="amount")
+print(long.to_dict(orient="records"))
+```
+
+```text
+[{'item': 'A', 'month': 'jan', 'amount': 10}, {'item': 'A', 'month': 'feb', 'amount': 20}]
+```
+
+`melt()` resulta especialmente útil cuando columnas repetidas representan valores de una misma variable conceptual.
+
+## 43. `read_csv()` convierte texto delimitado en DataFrame
+
+`pd.read_csv()` es una de las funciones de I/O más importantes de pandas.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+source = StringIO("id,amount\n1,10.5\n2,20.0\n")
+table = pd.read_csv(source)
+print(table.shape)
+```
+
+```text
+(2, 2)
+```
+
+pandas infiere un schema salvo que proporciones instrucciones más fuertes. La inferencia es comodidad, no contrato de negocio.
+
+## 44. Controla el parsing de CSV cuando conoces el schema
+
+Argumentos útiles de `read_csv()` incluyen `usecols`, `dtype`, `parse_dates`, `na_values`, `encoding` y opciones de delimitador.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+source = StringIO("code,date,amount\n001,2026-08-01,10.5\n")
+table = pd.read_csv(
+ source,
+ dtype={"code": "str"},
+ parse_dates=["date"],
+)
+print(table.loc[0, "code"])
+print(table.loc[0, "date"].year)
+```
+
+```text
+001
+2026
+```
+
+Dar a pandas información conocida del schema reduce inferencias accidentales y documenta expectativas cerca de la frontera de entrada.
+
+## 45. Los identificadores suelen pertenecer al dtype string
+
+Un código como `00123` puede parecer numérico sin tener significado aritmético. Convertirlo a entero destruye ceros iniciales.
+
+```python
+import pandas as pd
+
+
+codes = pd.Series(["001", "010"], dtype="str")
+print(codes.tolist())
+```
+
+```text
+['001', '010']
+```
+
+Modela identificadores según su semántica, no por los caracteres que contienen.
+
+## 46. `to_csv()` debe hacer explícita la política del índice
+
+Para tablas cuyo índice solo es una etiqueta interna de fila, `index=False` evita una columna extra al reimportar.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+table = pd.DataFrame({"id": [1], "value": [10]})
+buffer = StringIO()
+table.to_csv(buffer, index=False)
+print(buffer.getvalue().strip())
+```
+
+```text
+id,value
+1,10
+```
+
+Si el índice contiene información real, expórtalo intencionalmente en vez de desactivarlo siempre.
+
+## 47. Method chains hacen visible el orden de transformación
+
+Una cadena corta puede leerse como pipeline: filtrar, derivar, ordenar, agrupar, exportar.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {"status": ["paid", "pending", "paid"], "amount": [30, 50, 20]}
+)
+result = (
+ orders.loc[orders["status"] == "paid"]
+ .assign(taxed=lambda frame: frame["amount"] * 1.1)
+ .sort_values("amount")
+)
+print(result["amount"].tolist())
+```
+
+```text
+[20, 30]
+```
+
+Las cadenas largas pueden dificultar debugging. Divídelas en etapas nombradas cuando la intención deje de ser clara.
+
+## 48. Prefiere operaciones vectorizadas a loops Python por fila
+
+Cuando un cálculo pueda expresarse con aritmética de `Series`, comparaciones, `.str`, `.dt` o reducciones nativas, prefiere esa forma.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"quantity": [2, 3], "price": [4.0, 5.0]})
+table["total"] = table["quantity"] * table["price"]
+print(table["total"].tolist())
+```
+
+```text
+[8.0, 15.0]
+```
+
+La vectorización comunica intención tabular y suele permitir que pandas/NumPy trabajen con mayor eficiencia que llamadas Python repetidas.
+
+## 49. `apply()` no es automáticamente vectorización
+
+`Series.apply()` y `DataFrame.apply()` por fila pueden servir para lógica Python personalizada, pero pueden ejecutar una función Python repetidamente.
+
+Antes de usar `apply()`, pregunta si pandas ya proporciona una operación nativa para la transformación.
+
+Úsalo porque la lógica personalizada sea realmente necesaria, no porque parezca más corto que un loop.
+
+## 50. Evita `iterrows()` para transformaciones normales
+
+La iteración por filas puede ser necesaria en fronteras con efectos externos, pero filtros, cálculos, agregaciones y asignaciones suelen tener formas mejores orientadas a columnas.
+
+Una fila devuelta por `iterrows()` es una representación `Series`. No la trates como un handle mutable para actualizar el DataFrame original.
+
+## 51. `.copy()` todavía tiene un papel deliberado
+
+Copy-on-Write significa que copias defensivas ya no son necesarias solo para silenciar el antiguo `SettingWithCopyWarning`.
+
+Usa `.copy()` cuando una copia independiente inmediata forme parte del diseño o del contrato de ciclo de vida.
+
+```python
+import pandas as pd
+
+
+original = pd.DataFrame({"value": [1, 2]})
+independent = original.copy()
+independent.loc[0, "value"] = 99
+print(original["value"].tolist())
+```
+
+```text
+[1, 2]
+```
+
+## 52. Los errores de DataFrame deben mantenerse visibles
+
+Fallos comunes incluyen:
+
+```text
+KeyError
+ValueError
+pandas.errors.ParserError
+pandas.errors.MergeError
+```
+
+No captures excepciones amplias solo para mantener un pipeline avanzando. Una tabla parcialmente transformada puede ser más peligrosa que un fallo visible.
+
+Los fallos de validación deben detener el flujo cuando continuar haría la salida no confiable.
+
+## 53. Ejemplo práctico: construir una pequeña tabla de ventas
+
+```python
+import pandas as pd
+
+
+data = {
+ "product": ["Notebook", "Keyboard", "Mouse"],
+ "units": [2, 5, 8],
+ "unit_price": [3500.0, 180.0, 95.0],
+}
+
+sales = pd.DataFrame(data)
+sales["total"] = sales["units"] * sales["unit_price"]
+
+print(f"shape: {sales.shape}")
+print(f"columns: {sales.columns.tolist()}")
+print(f"grand total: {sales['total'].sum():.2f}")
+```
+
+```text
+shape: (3, 4)
+columns: ['product', 'units', 'unit_price', 'total']
+grand total: 8660.00
+```
+
+Este ejemplo refleja `examples/dataframe_basics.py` y demuestra construcción, inspección, columna derivada y agregación.
+
+## 54. Ejemplo práctico: filtrar y asignar con seguridad
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [101, 102, 103, 104],
+ "status": ["paid", "pending", "paid", "paid"],
+ "amount": [120.0, 80.0, 250.0, 90.0],
+ }
+)
+
+orders["priority"] = "normal"
+orders.loc[
+ (orders["status"] == "paid") & (orders["amount"] >= 200),
+ "priority",
+] = "high"
+
+selected = orders.loc[
+ orders["status"] == "paid",
+ ["order_id", "priority"],
+]
+print(selected.to_dict(orient="records"))
+```
+
+```text
+[{'order_id': 101, 'priority': 'normal'}, {'order_id': 103, 'priority': 'high'}, {'order_id': 104, 'priority': 'normal'}]
+```
+
+La actualización ocurre directamente sobre `orders` mediante `.loc`, el patrón seguro para pandas 3.0.
+
+## 55. Ejemplo práctico: resumen agrupado
+
+```python
+import pandas as pd
+
+
+transactions = pd.DataFrame(
+ {
+ "category": ["books", "games", "books", "games", "office"],
+ "amount": [40.0, 120.0, 35.0, 80.0, 25.0],
+ }
+)
+
+summary = (
+ transactions.groupby("category", as_index=False)
+ .agg(
+ total_amount=("amount", "sum"),
+ transaction_count=("amount", "size"),
+ )
+ .sort_values("category")
+)
+
+print(summary.to_dict(orient="records"))
+```
+
+Las columnas de salida nombradas forman un schema estable. El sort final hace determinista el ejemplo.
+
+## 56. Ejemplo práctico: merge validado
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [1, 2, 3],
+ "customer_id": [10, 20, 10],
+ "amount": [50.0, 80.0, 30.0],
+ }
+)
+customers = pd.DataFrame(
+ {
+ "customer_id": [10, 20],
+ "customer": ["Aster", "Boreal"],
+ }
+)
+
+report = orders.merge(
+ customers,
+ on="customer_id",
+ how="left",
+ validate="many_to_one",
+)
+report = report[["order_id", "customer", "amount"]].sort_values("order_id")
+
+print(report.to_dict(orient="records"))
+```
+
+`validate="many_to_one"` documenta que muchos pedidos pueden referenciar a un cliente mientras la tabla de clientes debe mantener claves únicas.
+
+## 57. Ejemplo práctico: pipeline CSV determinista
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pandas as pd
+
+
+with TemporaryDirectory() as temp_dir:
+ workspace = Path(temp_dir)
+ source = workspace / "orders.csv"
+ destination = workspace / "paid_orders.csv"
+
+ source.write_text(
+ "order_id,date,status,amount\n"
+ "1,2026-08-01,paid,120.50\n"
+ "2,2026-08-02,pending,80.00\n"
+ "3,2026-08-03,paid,250.00\n",
+ encoding="utf-8",
+ )
+
+ orders = pd.read_csv(source, parse_dates=["date"])
+ paid_orders = orders.loc[orders["status"] == "paid"].sort_values("order_id")
+ paid_orders.to_csv(destination, index=False)
+
+ print(f"rows: {len(paid_orders)}")
+ print(f"total: {paid_orders['amount'].sum():.2f}")
+ print(f"saved: {destination.name}")
+```
+
+```text
+rows: 2
+total: 370.50
+saved: paid_orders.csv
+```
+
+El directorio temporal mantiene seguro el ejemplo, `parse_dates` establece semántica datetime en la entrada, el sort estabiliza el resultado y `index=False` mantiene deliberado el schema CSV.
+
+## 58. Errores comunes
+
+Evita estos patrones:
+
+- tratar pandas como reemplazo de toda lista o diccionario;
+- confiar en dtypes inferidos sin inspección;
+- convertir identificadores a números solo porque contienen dígitos;
+- usar chained assignment en vez de una actualización `.loc` única;
+- llamar `dropna()` o `fillna()` sin definir el significado de ausencia;
+- unir tablas sin comprobar unicidad o cardinalidad de claves;
+- depender del orden incidental de filas;
+- usar `iterrows()` para cálculos que tienen formas vectorizadas;
+- usar `apply()` antes de comprobar operaciones nativas de pandas;
+- exportar accidentalmente un índice interno;
+- ocultar errores de parsing o merge y continuar con datos parciales;
+- copiar consejos de pandas 1.x/2.x sin comprobar el comportamiento de pandas 3.0.
+
+## 59. Tabla de decisión
+
+| Necesidad | Prefiere |
+|---|---|
+| una columna etiquetada | `Series` |
+| tabla etiquetada | `DataFrame` |
+| selección por label | `.loc` |
+| selección por posición | `.iloc` |
+| filtro condicional de filas | máscara booleana + `.loc` |
+| actualización condicional | una asignación `.loc[...] = ...` |
+| convertir texto numérico | `pd.to_numeric()` |
+| convertir texto datetime | `pd.to_datetime()` / `parse_dates` |
+| inspeccionar ausencias | `isna()` |
+| eliminar filas bajo una regla definida | `dropna()` |
+| rellenar ausencias bajo una regla definida | `fillna()` |
+| agregar por grupo | `groupby()` + `agg()` |
+| estadística de grupo junto a cada fila | `groupby()` + `transform()` |
+| join por clave estilo base de datos | `merge()` + `validate=` cuando se conozca |
+| apilar tablas compatibles | `concat()` |
+| matriz de resumen | `pivot_table()` |
+| reshape wide-to-long | `melt()` |
+| cargar CSV | `read_csv()` |
+| guardar CSV | `to_csv(index=...)` |
+
+## 60. Referencia rápida
+
+```text
+import pandas as pd
+
+pd.Series(...)
+pd.DataFrame(...)
+
+df.shape
+df.columns
+df.dtypes
+df.head()
+df.info()
+df.describe()
+
+df["column"]
+df[["column_a", "column_b"]]
+df.loc[...]
+df.iloc[...]
+
+df.assign(...)
+df.rename(...)
+df.drop(...)
+df.sort_values(...)
+df.sort_index(...)
+
+df.isna()
+df.dropna(...)
+df.fillna(...)
+df.astype(...)
+pd.to_numeric(...)
+pd.to_datetime(...)
+
+series.str...
+series.dt...
+series.value_counts()
+series.nunique()
+
+df.groupby(...)
+df.agg(...)
+df.transform(...)
+
+df.merge(...)
+pd.concat(...)
+df.pivot_table(...)
+df.melt(...)
+
+pd.read_csv(...)
+df.to_csv(...)
+```
+
+## 61. Checklist de diseño
+
+Antes de aceptar una transformación pandas, pregunta:
+
+- ¿Cuál es el schema esperado de entrada?
+- ¿Qué columnas son identificadores, números, texto, fechas o categorías?
+- ¿El índice es significativo o solo posicional?
+- ¿La alineación por etiquetas puede cambiar el resultado?
+- ¿Se permiten valores ausentes y qué significan?
+- ¿La inferencia de dtype es aceptable en esta frontera?
+- ¿Las actualizaciones condicionales se hacen directamente con `.loc`?
+- ¿La cardinalidad del merge se conoce y valida?
+- ¿El orden de filas puede variar y debería ordenarse el resultado?
+- ¿Existe una operación vectorizada disponible?
+- ¿`apply()` o la iteración por filas realmente requieren lógica Python?
+- ¿Una exportación incluirá el índice accidentalmente?
+- ¿Los fallos permanecen visibles en vez de convertirse silenciosamente?
+- ¿Está documentado el contrato de versión de pandas?
+- ¿El código depende de supuestos copy/view anteriores a pandas 3.0?
+
+## 62. Ejercicio
+
+Construye un pipeline ficticio de análisis de pedidos:
+
+1. Crea o carga un CSV con `order_id`, `customer_id`, `date`, `status`, `category` y `amount`.
+2. Conserva identificadores como strings si se permiten ceros iniciales.
+3. Convierte `date` a datetime.
+4. Valida columnas requeridas antes de transformar.
+5. Convierte `amount` numéricamente y detecta entradas inválidas.
+6. Informa valores ausentes por columna.
+7. Conserva solo filas `paid` sin un loop Python por fila.
+8. Crea una columna derivada `month` desde los datetime.
+9. Produce un resumen por `category` con total, promedio y cantidad de transacciones.
+10. Une los pedidos con una tabla ficticia de clientes y valida la cardinalidad esperada.
+11. Ordena explícitamente la salida del reporte.
+12. Guarda el resumen final sin exportar un índice accidental.
+13. Haz visibles los fallos esperados de calidad en vez de ocultarlos.
+
+Desafíos de extensión:
+
+- compara una solución vectorizada con una basada en `apply()`;
+- construye una pivot table wide;
+- conviértela de nuevo a long con `melt()`;
+- agrega tests para conteos de filas, totales, unicidad de claves, dtypes y cardinalidad de merge;
+- documenta qué transformaciones cambian la cantidad de filas y por qué.
+
+## 63. Conexiones con conceptos anteriores de Python
+
+`pandas` se apoya en conceptos ya estudiados:
+
+- **listas y diccionarios:** constructores y conversiones de resultados;
+- **funciones:** etapas reutilizables de transformación;
+- **lógica booleana:** máscaras de filas;
+- **excepciones:** fallos visibles de I/O, conversión y joins;
+- **archivos y context managers:** fronteras CSV y otros datos;
+- **`pathlib`:** objetos de ruta funcionan naturalmente con I/O de pandas;
+- **`datetime`:** pandas extiende el trabajo temporal a columnas;
+- **CSV y JSON:** pandas añade una capa orientada a tablas sobre formatos de datos;
+- **`decimal`:** las decisiones de representación siguen importando; columnas float no reemplazan requisitos de decimal exacto;
+- **`logging`:** pipelines operativos deben reportar contexto útil sin ocultar excepciones;
+- **`os` y `shutil`:** descubrimiento y movimiento de archivos suelen rodear pipelines pandas.
+
+## 64. Referencias
+
+Referencias principales usadas para este capítulo:
+
+- [Documentación pandas 3.0.5](https://pandas.pydata.org/docs/)
+- [Pandas User Guide](https://pandas.pydata.org/docs/user_guide/)
+- [Visión general del paquete pandas](https://pandas.pydata.org/docs/getting_started/overview.html)
+- [Tutoriales Getting Started](https://pandas.pydata.org/docs/getting_started/intro_tutorials/)
+- [Copy-on-Write](https://pandas.pydata.org/docs/user_guide/copy_on_write.html)
+- [Notas de versión de pandas 3.0.0](https://pandas.pydata.org/docs/whatsnew/v3.0.0.html)
+
+La documentación oficial identifica pandas 3.0.5 como la documentación estable usada en este capítulo, y pandas 3.0 requiere Python 3.11 o superior.
+
+## 65. Siguiente capítulo
+
+Este capítulo abre la **Fase 9: Bibliotecas Externas**.
+
+La siguiente biblioteca planificada es **`openpyxl`**, enfocada en operaciones programáticas sobre libros de Excel.
+
+Antes de avanzar, practica pandas con datasets suficientemente pequeños para poder inspeccionarlos manualmente. Una biblioteca tabular solo se vuelve útil cuando todavía puedes razonar sobre lo que debería hacer cada transformación.
diff --git a/external-libraries/01-pandas/README.md b/external-libraries/01-pandas/README.md
new file mode 100644
index 0000000..b03f008
--- /dev/null
+++ b/external-libraries/01-pandas/README.md
@@ -0,0 +1,1374 @@
+
+
+# Working with Tabular Data Using `pandas`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Back to External Libraries](../README.md) · [← Previous phase: `os` + `shutil`](../../standard-library/09-os-shutil/README.md)
+
+Phase 9 begins where the standard library stops: adding third-party packages with their own release cycles, dependency contracts, and domain-specific abstractions.
+
+`pandas` is the first external library because it connects directly to concepts already studied: lists, dictionaries, CSV, JSON, dates, files, functions, exceptions, paths, and data validation. The new challenge is not merely learning methods. It is learning to preserve **table semantics** while transformations become more expressive.
+
+This chapter targets **pandas 3.0.x** and was researched against the official pandas **3.0.5** documentation. pandas 3.0 supports Python 3.11 and newer.
+
+**Estimated study time:** 240–330 minutes.
+
+## Learning goals
+
+By the end of this chapter, you should be able to:
+
+- explain when pandas is a better fit than built-in collections;
+- create and inspect `Series` and `DataFrame` objects;
+- reason about indexes, labels, alignment, columns, and dtypes;
+- select rows and columns with brackets, `.loc`, and `.iloc`;
+- build boolean masks and update rows safely;
+- understand pandas 3.0 Copy-on-Write and why chained assignment is not valid;
+- handle missing values with an explicit policy;
+- parse numeric, string, and datetime columns deliberately;
+- aggregate with `groupby()`, `agg()`, and `transform()`;
+- combine tables with validated `merge()` and `concat()`;
+- reshape data with `pivot_table()` and `melt()`;
+- load and save CSV data with explicit schema decisions;
+- prefer vectorized operations when they express the problem;
+- recognize when `apply()` and row iteration are poor defaults;
+- build deterministic, reviewable tabular-data pipelines.
+
+## 1. Why `pandas` exists
+
+`pandas` is a third-party library for labeled and tabular data. It is especially useful when data has rows, columns, labels, missing values, mixed column types, or needs filtering, grouping, joining, reshaping, and file-based input/output.
+
+It does not replace Python collections. A list or dictionary is often better for small application state. `pandas` becomes attractive when the problem is primarily a data table and operations apply to columns or groups of rows.
+
+## 2. External libraries introduce dependency contracts
+
+Unlike the standard library, pandas must be installed into the Python environment that will run the code. The repository declares executable Phase 9 dependencies in `requirements-external.txt`.
+
+A dependency contract answers questions such as:
+
+```text
+Which package is required?
+Which versions are supported by the chapter?
+Which Python versions are supported by that package?
+How does CI reproduce the same environment?
+Which behaviors changed between major versions?
+```
+
+This chapter deliberately targets pandas 3.0.x instead of pretending every historical pandas version behaves the same.
+
+## 3. Install pandas in an isolated environment
+
+A virtual environment keeps project dependencies separate from unrelated Python installations.
+
+```bash
+python -m venv .venv
+```
+
+Activate it according to your operating system, then install the repository dependency contract:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+The official pandas installation documentation also supports direct installation with `pip install pandas` and installation through conda-forge. A project dependency file is preferable here because it makes the study guide's executable contract reproducible.
+
+## 4. Import pandas with the conventional alias
+
+The pandas documentation and community convention use `pd`:
+
+```python
+import pandas as pd
+```
+
+Following the convention makes examples easier to compare with official documentation and other projects.
+
+## 5. `Series` models one labeled dimension
+
+A `Series` is a one-dimensional labeled data structure. It combines values with an index. A DataFrame column is commonly exposed as a `Series`.
+
+```python
+import pandas as pd
+
+
+scores = pd.Series([8.5, 9.0, 7.5], index=["A", "B", "C"])
+print(scores.loc["B"])
+```
+
+```text
+9.0
+```
+
+A `Series` is not simply a list with more methods. Labels participate in selection and alignment.
+
+## 6. `DataFrame` models a labeled table
+
+A `DataFrame` is a two-dimensional table with labeled rows and columns. Different columns may have different dtypes, which makes it suitable for many spreadsheet-, SQL-, and CSV-like datasets.
+
+```python
+import pandas as pd
+
+
+people = pd.DataFrame(
+ {
+ "name": ["Ana", "Bruno"],
+ "age": [28, 34],
+ "active": [True, False],
+ }
+)
+print(people.shape)
+```
+
+```text
+(2, 3)
+```
+
+A dictionary of equally sized sequences is one of the clearest constructors for small examples. Dictionary keys become column labels.
+
+## 7. The index is part of the data model
+
+The index labels rows. The default `RangeIndex` is often perfectly adequate. Use a meaningful custom index only when row labels genuinely participate in selection, alignment, or identity.
+
+```python
+import pandas as pd
+
+
+temperatures = pd.Series([21.5, 19.0], index=["morning", "evening"])
+print(temperatures.index.tolist())
+```
+
+```text
+['morning', 'evening']
+```
+
+Do not convert every business identifier into an index automatically. A normal column is often easier to validate, merge, export, and explain.
+
+## 8. Label alignment is powerful and can surprise you
+
+When pandas combines labeled objects, it generally aligns values by index labels rather than blindly by physical position.
+
+```python
+import pandas as pd
+
+
+left = pd.Series([10, 20], index=["a", "b"])
+right = pd.Series([1, 2], index=["b", "c"])
+print((left + right).to_dict())
+```
+
+The shared label `b` receives a value from both objects. Labels present on only one side become missing in the result.
+
+Treat the index as data, not decoration. Unexpected labels can change arithmetic, joins, assignments, and comparisons.
+
+## 9. Inspect columns and dtypes early
+
+A reliable data workflow inspects what was loaded before transforming it. `columns` reveals labels and `dtypes` reveals the dtype chosen for each column.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"label": ["x", "y"], "count": [1, 2]})
+print(table.columns.tolist())
+print(table.dtypes.astype(str).to_dict())
+```
+
+pandas 3.0 changed an important default: columns containing only strings are inferred as the dedicated `str` dtype instead of the historical generic `object` dtype.
+
+That is one reason this chapter states its pandas version explicitly.
+
+## 10. `shape`, `size`, and `ndim` answer different questions
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+print(table.shape, table.size, table.ndim)
+```
+
+```text
+(3, 2) 6 2
+```
+
+- `shape` returns `(rows, columns)`;
+- `size` returns the number of cells;
+- `ndim` returns the number of dimensions.
+
+These are structural facts, not validation by themselves.
+
+## 11. Previewing data is useful but not validation
+
+`head()` and `tail()` are quick inspection tools. `sample()` can also reveal patterns away from the first rows, but use `random_state` when reproducible output matters.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [10, 20, 30, 40]})
+print(table.sample(2, random_state=7)["value"].tolist())
+```
+
+A preview does not prove that required columns exist, dtypes are correct, identifiers are unique, or values fall inside allowed ranges.
+
+## 12. `info()` and `describe()` answer different inspection questions
+
+`DataFrame.info()` summarizes row count, column names, non-null counts, dtypes, and approximate memory use. It is useful for human inspection.
+
+`describe()` summarizes statistics such as count, mean, spread, and extrema for appropriate columns.
+
+```python
+import pandas as pd
+
+
+values = pd.DataFrame({"amount": [10.0, 20.0, 30.0]})
+print(values["amount"].describe()[["count", "mean", "max"]].to_dict())
+```
+
+Neither function understands the business meaning of the data. A negative amount may be mathematically valid but invalid for a particular dataset. An identifier may be numeric-looking but meaningless to average.
+
+## 13. Select one column with brackets
+
+`df["column"]` returns a `Series`.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"unit price": [10.0, 12.5]})
+prices = table["unit price"]
+print(type(prices).__name__)
+```
+
+```text
+Series
+```
+
+Prefer bracket syntax over attribute access such as `df.column`. Column names may contain spaces, conflict with DataFrame attributes, or be chosen dynamically.
+
+## 14. Select multiple columns with a list
+
+Passing a list of column labels returns a DataFrame and preserves the requested column order.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"a": [1], "b": [2], "c": [3]})
+subset = table[["c", "a"]]
+print(subset.columns.tolist())
+```
+
+```text
+['c', 'a']
+```
+
+This distinction matters: a string selects one column as `Series`; a list of strings selects a table-shaped `DataFrame`.
+
+## 15. Use `.loc` for label-based selection
+
+`.loc` selects by labels and boolean conditions.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"status": ["new", "done"], "value": [5, 8]}, index=["a", "b"])
+print(table.loc["b", "value"])
+```
+
+```text
+8
+```
+
+`.loc` is also the preferred tool for conditional assignment because target rows and columns can be expressed in one operation.
+
+## 16. Use `.iloc` for positional selection
+
+`.iloc` selects by integer position, independent of index labels.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"name": ["first", "second", "third"]}, index=[10, 20, 30])
+print(table.iloc[1, 0])
+```
+
+```text
+second
+```
+
+Use `.iloc` when position itself is meaningful. Do not use it merely because label-based selection feels unfamiliar.
+
+## 17. Label slices and positional slices have different boundaries
+
+With `.loc`, a label slice includes the stop label when it exists. With `.iloc`, slicing follows normal Python positional slicing and excludes the stop position.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [10, 20, 30]}, index=["a", "b", "c"])
+print(table.loc["a":"b", "value"].tolist())
+print(table.iloc[0:2, 0].tolist())
+```
+
+```text
+[10, 20]
+[10, 20]
+```
+
+The two examples happen to return the same values for different reasons. Keep the two mental models separate.
+
+## 18. Boolean masks filter rows
+
+A comparison against a `Series` produces a boolean `Series`. Using that mask with `.loc` keeps rows where the condition is true.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"amount": [50, 120, 80]})
+mask = orders["amount"] >= 80
+print(orders.loc[mask, "amount"].tolist())
+```
+
+```text
+[120, 80]
+```
+
+Masks are one of the most important bridges between Python boolean logic and table-oriented operations.
+
+## 19. Combine masks with `&`, `|`, and `~`
+
+Use element-wise boolean operators for `Series` conditions and parenthesize each comparison.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {"status": ["paid", "paid", "pending"], "amount": [50, 150, 200]}
+)
+mask = (orders["status"] == "paid") & (orders["amount"] >= 100)
+print(orders.loc[mask, "amount"].tolist())
+```
+
+```text
+[150]
+```
+
+Python's scalar `and` and `or` do not express row-by-row logic for a pandas `Series`.
+
+## 20. Assign through the object you intend to change
+
+When updating a DataFrame, express the row selector and destination column in one `.loc` operation.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"amount": [50, 150], "priority": ["normal", "normal"]})
+orders.loc[orders["amount"] >= 100, "priority"] = "high"
+print(orders["priority"].tolist())
+```
+
+```text
+['normal', 'high']
+```
+
+This pattern is explicit and compatible with pandas 3.0 Copy-on-Write semantics.
+
+## 21. Copy-on-Write is the pandas 3.0 rule
+
+In pandas 3.0, objects derived through indexing or methods behave like copies from the user's perspective. Mutating a derived object does not mutate the original object.
+
+```python
+import pandas as pd
+
+
+original = pd.DataFrame({"value": [1, 2, 3]})
+subset = original["value"]
+subset.iloc[0] = 99
+
+print(original["value"].tolist())
+print(subset.tolist())
+```
+
+```text
+[1, 2, 3]
+[99, 2, 3]
+```
+
+Under the hood, pandas may share memory until a write requires a copy. The important contract for application code is the observable behavior.
+
+## 22. Chained assignment is not a valid update strategy
+
+Code such as this uses multiple indexing steps:
+
+```text
+df["value"][mask] = 10
+```
+
+In pandas 3.0, chained assignment does not update the original DataFrame. The old ambiguity that produced `SettingWithCopyWarning` has been replaced by a simpler rule: modify the object itself in one operation.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [1, 2, 3]})
+table.loc[table["value"] >= 2, "value"] = 10
+print(table["value"].tolist())
+```
+
+```text
+[1, 10, 10]
+```
+
+This is a major migration point from older pandas material found on the internet.
+
+## 23. Create derived columns with vectorized expressions
+
+Column expressions operate over whole `Series` objects and are usually clearer than writing a Python loop for every row.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"units": [2, 3], "unit_price": [10.0, 12.5]})
+sales["total"] = sales["units"] * sales["unit_price"]
+print(sales["total"].tolist())
+```
+
+```text
+[20.0, 37.5]
+```
+
+This is one of the central pandas habits: express a transformation in terms of columns when the rule itself is column-oriented.
+
+## 24. `assign()` is useful in method chains
+
+`assign()` returns a DataFrame with added or replaced columns.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"units": [2, 3], "price": [5.0, 8.0]})
+result = sales.assign(total=lambda frame: frame["units"] * frame["price"])
+print(result["total"].tolist())
+```
+
+```text
+[10.0, 24.0]
+```
+
+Use it when a pipeline becomes easier to read by keeping transformations chained. Direct assignment remains perfectly valid when it is clearer.
+
+## 25. Rename, drop, and sort with intent
+
+`rename()` can normalize awkward external column names. `drop()` removes rows or columns. `sort_values()` and `sort_index()` make ordering explicit.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"Order Amount": [20, 10], "temporary_note": ["b", "a"]})
+clean = (
+ table.rename(columns={"Order Amount": "amount"})
+ .drop(columns=["temporary_note"])
+ .sort_values("amount")
+)
+print(clean["amount"].tolist())
+```
+
+```text
+[10, 20]
+```
+
+A dropped field may be impossible to reconstruct later. A sort may be required for deterministic reports. These operations encode policy, not merely formatting.
+
+## 26. Missing data needs an explicit policy
+
+Missing values can mean unknown, not applicable, not collected, invalid, delayed, or intentionally blank. Those meanings are not interchangeable.
+
+Before calling `dropna()` or `fillna()`, decide what absence means for each relevant column.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [1.0, None, 3.0], "label": ["a", "b", None]})
+print(table.isna().sum().to_dict())
+```
+
+```text
+{'value': 1, 'label': 1}
+```
+
+Counting missing values is observation. Dropping or filling them is a transformation that requires a rule.
+
+## 27. `dropna()` discards observations
+
+`dropna()` is correct only when the affected observations are genuinely disposable under the data contract.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"id": [1, 2, 3], "amount": [10.0, None, 30.0]})
+complete = table.dropna(subset=["amount"])
+print(complete["id"].tolist())
+```
+
+```text
+[1, 3]
+```
+
+Using `dropna()` with no subset can remove rows because of fields that were not important to the current operation.
+
+## 28. `fillna()` inserts a chosen meaning
+
+Replacing an unknown amount with zero asserts that zero is the correct interpretation.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"discount": [0.1, None, 0.2]})
+filled = table["discount"].fillna(0.0)
+print(filled.tolist())
+```
+
+```text
+[0.1, 0.0, 0.2]
+```
+
+Document fill rules because they change the dataset, not just its appearance.
+
+## 29. Dtypes are part of the schema
+
+A column that looks numeric may have been loaded as text. A date may still be a string. An identifier may need to remain textual even when every value contains only digits.
+
+Use `astype()` when values are already valid for the target dtype:
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"units": ["1", "2", "3"]})
+table["units"] = table["units"].astype("int64")
+print(table["units"].sum())
+```
+
+```text
+6
+```
+
+Choose types according to meaning and operations, not appearance alone.
+
+## 30. `to_numeric()` makes parsing policy explicit
+
+`pd.to_numeric()` is useful when numeric parsing may fail.
+
+```python
+import pandas as pd
+
+
+raw = pd.Series(["10", "invalid", "30"])
+parsed = pd.to_numeric(raw, errors="coerce")
+print(parsed.isna().sum())
+```
+
+```text
+1
+```
+
+`errors="coerce"` converts invalid entries into missing values. That is only safe when the workflow subsequently audits and handles the newly missing values.
+
+## 31. String operations are vectorized under `.str`
+
+The `.str` accessor applies string operations to a `Series`.
+
+```python
+import pandas as pd
+
+
+names = pd.Series([" Alpha ", "BETA "])
+normalized = names.str.strip().str.lower()
+print(normalized.tolist())
+```
+
+```text
+['alpha', 'beta']
+```
+
+Normalize text only when normalization matches the domain contract. Lowercasing identifiers or preserving/discarding whitespace can change meaning.
+
+## 32. Parse datetimes before using datetime semantics
+
+Use `pd.to_datetime()` when text should become actual datetime values.
+
+```python
+import pandas as pd
+
+
+dates = pd.to_datetime(pd.Series(["2026-08-01", "2026-08-03"]), format="%Y-%m-%d")
+print((dates.iloc[1] - dates.iloc[0]).days)
+```
+
+```text
+2
+```
+
+The `.dt` accessor then exposes vectorized components:
+
+```python
+import pandas as pd
+
+
+dates = pd.to_datetime(pd.Series(["2026-01-15", "2026-02-20"]))
+print(dates.dt.month.tolist())
+```
+
+```text
+[1, 2]
+```
+
+Ambiguous date formats should be controlled explicitly instead of guessed.
+
+## 33. Duplicates require a definition
+
+Two rows are duplicates only relative to chosen columns. `duplicated()` and `drop_duplicates()` accept `subset` so the workflow can express the actual uniqueness key.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame(
+ {"id": [1, 1, 2], "note": ["first", "repeated", "other"]}
+)
+print(table.duplicated(subset=["id"]).tolist())
+```
+
+```text
+[False, True, False]
+```
+
+Do not deduplicate entire rows when the real rule is uniqueness by an identifier.
+
+## 34. Frequency and summary methods are compact diagnostics
+
+`value_counts()` exposes category frequency. `nunique()` counts distinct non-missing values by default. Reductions such as `sum()`, `mean()`, `min()`, `max()`, and `count()` summarize columns.
+
+```python
+import pandas as pd
+
+
+statuses = pd.Series(["paid", "pending", "paid", "paid"])
+print(statuses.value_counts().sort_index().to_dict())
+```
+
+```text
+{'paid': 3, 'pending': 1}
+```
+
+A frequency is evidence about the observed dataset, not proof that every observed category is allowed.
+
+## 35. `groupby()` implements split-apply-combine
+
+`groupby()` splits rows by one or more keys, applies aggregation or transformation, and combines results.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {"category": ["A", "B", "A"], "amount": [10, 20, 30]}
+)
+summary = sales.groupby("category")["amount"].sum()
+print(summary.to_dict())
+```
+
+```text
+{'A': 40, 'B': 20}
+```
+
+Grouping is one of pandas' central tools because many analytical questions are really "calculate something per category, customer, date, region, or other key."
+
+## 36. Named aggregation makes the output schema explicit
+
+Named aggregation lets the output state both the source column and the operation.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {"category": ["A", "A", "B"], "amount": [10.0, 30.0, 20.0]}
+)
+summary = sales.groupby("category", as_index=False).agg(
+ total=("amount", "sum"),
+ average=("amount", "mean"),
+)
+print(summary.to_dict(orient="records"))
+```
+
+```text
+[{'category': 'A', 'total': 40.0, 'average': 20.0}, {'category': 'B', 'total': 20.0, 'average': 20.0}]
+```
+
+A stable output schema makes later validation, export, and testing easier.
+
+## 37. `transform()` keeps results aligned to original rows
+
+Unlike a normal aggregation, `transform()` returns a result aligned to the original row index.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"team": ["A", "A", "B"], "score": [10, 20, 30]})
+sales["team_total"] = sales.groupby("team")["score"].transform("sum")
+print(sales["team_total"].tolist())
+```
+
+```text
+[30, 30, 30]
+```
+
+This is useful when a group-level statistic must remain beside each observation.
+
+## 38. `merge()` combines tables by keys
+
+`merge()` is pandas' database-style join operation.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"customer_id": [1, 2], "amount": [10, 20]})
+customers = pd.DataFrame({"customer_id": [1, 2], "name": ["A", "B"]})
+result = orders.merge(customers, on="customer_id", how="left")
+print(result["name"].tolist())
+```
+
+```text
+['A', 'B']
+```
+
+A merge that runs without error can still be logically wrong if keys are duplicated unexpectedly.
+
+## 39. Validate merge cardinality
+
+The `validate` argument can assert relationships such as `one_to_one`, `one_to_many`, `many_to_one`, or `many_to_many`.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"customer_id": [1, 1], "amount": [10, 20]})
+customers = pd.DataFrame({"customer_id": [1], "name": ["A"]})
+result = orders.merge(
+ customers,
+ on="customer_id",
+ how="left",
+ validate="many_to_one",
+)
+print(len(result))
+```
+
+```text
+2
+```
+
+When cardinality is part of the data contract, validating it turns accidental key duplication into a visible failure instead of silent row multiplication.
+
+## 40. `concat()` stacks compatible objects
+
+`pd.concat()` combines pandas objects along an axis. Concatenating rows is common when multiple files share the same schema.
+
+```python
+import pandas as pd
+
+
+first = pd.DataFrame({"id": [1, 2]})
+second = pd.DataFrame({"id": [3]})
+combined = pd.concat([first, second], ignore_index=True)
+print(combined["id"].tolist())
+```
+
+```text
+[1, 2, 3]
+```
+
+After concatenation, decide whether original index labels must be preserved or reset.
+
+## 41. `pivot_table()` summarizes into a matrix
+
+A pivot table groups data across row and column dimensions and aggregates values.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {
+ "region": ["north", "north", "south"],
+ "product": ["A", "B", "A"],
+ "amount": [10, 20, 30],
+ }
+)
+pivot = sales.pivot_table(
+ index="region",
+ columns="product",
+ values="amount",
+ aggfunc="sum",
+ fill_value=0,
+)
+print(pivot.loc["north", "B"])
+```
+
+```text
+20
+```
+
+Use a pivot table when the desired output is itself a summary matrix.
+
+## 42. `melt()` converts wide data to long form
+
+Long-form data often makes grouping and visualization easier.
+
+```python
+import pandas as pd
+
+
+wide = pd.DataFrame({"item": ["A"], "jan": [10], "feb": [20]})
+long = wide.melt(id_vars="item", var_name="month", value_name="amount")
+print(long.to_dict(orient="records"))
+```
+
+```text
+[{'item': 'A', 'month': 'jan', 'amount': 10}, {'item': 'A', 'month': 'feb', 'amount': 20}]
+```
+
+`melt()` is especially useful when repeated columns actually represent values of one conceptual variable.
+
+## 43. `read_csv()` turns delimited text into a DataFrame
+
+`pd.read_csv()` is one of pandas' most important I/O functions.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+source = StringIO("id,amount\n1,10.5\n2,20.0\n")
+table = pd.read_csv(source)
+print(table.shape)
+```
+
+```text
+(2, 2)
+```
+
+pandas infers a schema unless you provide stronger instructions. Inference is convenience, not a business contract.
+
+## 44. Control CSV parsing when the schema is known
+
+Useful `read_csv()` arguments include `usecols`, `dtype`, `parse_dates`, `na_values`, `encoding`, and delimiter-related settings.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+source = StringIO("code,date,amount\n001,2026-08-01,10.5\n")
+table = pd.read_csv(
+ source,
+ dtype={"code": "str"},
+ parse_dates=["date"],
+)
+print(table.loc[0, "code"])
+print(table.loc[0, "date"].year)
+```
+
+```text
+001
+2026
+```
+
+Giving pandas known schema information reduces accidental inference and documents expectations near the input boundary.
+
+## 45. Identifiers often belong to string dtype
+
+A code such as `00123` may look numeric but have no arithmetic meaning. Parsing it as an integer destroys leading zeros.
+
+```python
+import pandas as pd
+
+
+codes = pd.Series(["001", "010"], dtype="str")
+print(codes.tolist())
+```
+
+```text
+['001', '010']
+```
+
+Model identifiers according to semantics, not the characters they happen to contain.
+
+## 46. `to_csv()` should make index policy explicit
+
+For ordinary tables whose index is only an internal row label, `index=False` prevents an extra index column from appearing on re-import.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+table = pd.DataFrame({"id": [1], "value": [10]})
+buffer = StringIO()
+table.to_csv(buffer, index=False)
+print(buffer.getvalue().strip())
+```
+
+```text
+id,value
+1,10
+```
+
+If the index carries real information, export it intentionally instead of always disabling it.
+
+## 47. Method chains make transformation order visible
+
+A short chain can read like a pipeline: filter, derive, sort, group, export.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {"status": ["paid", "pending", "paid"], "amount": [30, 50, 20]}
+)
+result = (
+ orders.loc[orders["status"] == "paid"]
+ .assign(taxed=lambda frame: frame["amount"] * 1.1)
+ .sort_values("amount")
+)
+print(result["amount"].tolist())
+```
+
+```text
+[20, 30]
+```
+
+Long chains can become difficult to debug. Break them into named stages when the intent stops being obvious.
+
+## 48. Prefer vectorized operations to Python row loops
+
+When a calculation can be expressed as `Series` arithmetic, comparisons, `.str`, `.dt`, or built-in reductions, prefer that form.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"quantity": [2, 3], "price": [4.0, 5.0]})
+table["total"] = table["quantity"] * table["price"]
+print(table["total"].tolist())
+```
+
+```text
+[8.0, 15.0]
+```
+
+Vectorization communicates table intent and usually lets pandas/NumPy perform work more efficiently than repeated Python calls.
+
+## 49. `apply()` is not automatically vectorization
+
+`Series.apply()` and row-wise `DataFrame.apply()` can be useful for custom Python logic, but they may execute a Python function repeatedly.
+
+Before using `apply()`, ask whether pandas already provides a native operation for the transformation.
+
+Use `apply()` because custom logic is genuinely needed, not because it looks shorter than a loop.
+
+## 50. Avoid `iterrows()` for ordinary transformations
+
+Row iteration is sometimes necessary at external side-effect boundaries, but ordinary filtering, calculations, aggregations, and assignments normally have better column-oriented forms.
+
+A row returned by `iterrows()` is a `Series` representation. Do not treat it as a mutable handle for updating the original DataFrame.
+
+## 51. `.copy()` still has a deliberate role
+
+Copy-on-Write means defensive copies are no longer required merely to silence the old `SettingWithCopyWarning`.
+
+Use `.copy()` when an eager independent copy is itself part of the design or lifetime contract.
+
+```python
+import pandas as pd
+
+
+original = pd.DataFrame({"value": [1, 2]})
+independent = original.copy()
+independent.loc[0, "value"] = 99
+print(original["value"].tolist())
+```
+
+```text
+[1, 2]
+```
+
+## 52. DataFrame errors should stay visible
+
+Common failures include:
+
+```text
+KeyError
+ValueError
+pandas.errors.ParserError
+pandas.errors.MergeError
+```
+
+Do not catch broad exceptions merely to keep a pipeline moving. A partially transformed table can be more dangerous than a visible failure.
+
+Validation failures should stop a workflow when continuing would make the output untrustworthy.
+
+## 53. Practical example: build a small sales table
+
+```python
+import pandas as pd
+
+
+data = {
+ "product": ["Notebook", "Keyboard", "Mouse"],
+ "units": [2, 5, 8],
+ "unit_price": [3500.0, 180.0, 95.0],
+}
+
+sales = pd.DataFrame(data)
+sales["total"] = sales["units"] * sales["unit_price"]
+
+print(f"shape: {sales.shape}")
+print(f"columns: {sales.columns.tolist()}")
+print(f"grand total: {sales['total'].sum():.2f}")
+```
+
+```text
+shape: (3, 4)
+columns: ['product', 'units', 'unit_price', 'total']
+grand total: 8660.00
+```
+
+This example mirrors `examples/dataframe_basics.py` and demonstrates construction, inspection, a derived column, and aggregation.
+
+## 54. Practical example: filter and assign safely
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [101, 102, 103, 104],
+ "status": ["paid", "pending", "paid", "paid"],
+ "amount": [120.0, 80.0, 250.0, 90.0],
+ }
+)
+
+orders["priority"] = "normal"
+orders.loc[
+ (orders["status"] == "paid") & (orders["amount"] >= 200),
+ "priority",
+] = "high"
+
+selected = orders.loc[
+ orders["status"] == "paid",
+ ["order_id", "priority"],
+]
+print(selected.to_dict(orient="records"))
+```
+
+```text
+[{'order_id': 101, 'priority': 'normal'}, {'order_id': 103, 'priority': 'high'}, {'order_id': 104, 'priority': 'normal'}]
+```
+
+The update happens directly on `orders` through `.loc`, which is the pandas 3.0-safe pattern.
+
+## 55. Practical example: grouped summary
+
+```python
+import pandas as pd
+
+
+transactions = pd.DataFrame(
+ {
+ "category": ["books", "games", "books", "games", "office"],
+ "amount": [40.0, 120.0, 35.0, 80.0, 25.0],
+ }
+)
+
+summary = (
+ transactions.groupby("category", as_index=False)
+ .agg(
+ total_amount=("amount", "sum"),
+ transaction_count=("amount", "size"),
+ )
+ .sort_values("category")
+)
+
+print(summary.to_dict(orient="records"))
+```
+
+The named output columns form a stable summary schema. The final sort makes the example deterministic.
+
+## 56. Practical example: validated merge
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [1, 2, 3],
+ "customer_id": [10, 20, 10],
+ "amount": [50.0, 80.0, 30.0],
+ }
+)
+customers = pd.DataFrame(
+ {
+ "customer_id": [10, 20],
+ "customer": ["Aster", "Boreal"],
+ }
+)
+
+report = orders.merge(
+ customers,
+ on="customer_id",
+ how="left",
+ validate="many_to_one",
+)
+report = report[["order_id", "customer", "amount"]].sort_values("order_id")
+
+print(report.to_dict(orient="records"))
+```
+
+`validate="many_to_one"` documents that many orders may reference one customer while the customer lookup must keep unique keys.
+
+## 57. Practical example: deterministic CSV pipeline
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pandas as pd
+
+
+with TemporaryDirectory() as temp_dir:
+ workspace = Path(temp_dir)
+ source = workspace / "orders.csv"
+ destination = workspace / "paid_orders.csv"
+
+ source.write_text(
+ "order_id,date,status,amount\n"
+ "1,2026-08-01,paid,120.50\n"
+ "2,2026-08-02,pending,80.00\n"
+ "3,2026-08-03,paid,250.00\n",
+ encoding="utf-8",
+ )
+
+ orders = pd.read_csv(source, parse_dates=["date"])
+ paid_orders = orders.loc[orders["status"] == "paid"].sort_values("order_id")
+ paid_orders.to_csv(destination, index=False)
+
+ print(f"rows: {len(paid_orders)}")
+ print(f"total: {paid_orders['amount'].sum():.2f}")
+ print(f"saved: {destination.name}")
+```
+
+```text
+rows: 2
+total: 370.50
+saved: paid_orders.csv
+```
+
+The temporary directory keeps the example safe, `parse_dates` establishes datetime semantics at input, sorting stabilizes the result, and `index=False` keeps the CSV schema deliberate.
+
+## 58. Common mistakes
+
+Avoid these patterns:
+
+- treating pandas as a replacement for every list or dictionary;
+- trusting inferred dtypes without inspection;
+- converting identifiers to numbers because they contain digits;
+- using chained assignment instead of one `.loc` update;
+- calling `dropna()` or `fillna()` without defining missing-value meaning;
+- joining tables without checking key uniqueness or cardinality;
+- relying on incidental row order;
+- using `iterrows()` for calculations that have vectorized forms;
+- using `apply()` before checking native pandas operations;
+- exporting an internal index accidentally;
+- swallowing parsing or merge errors and continuing with partial data;
+- copying older pandas 1.x/2.x advice without checking pandas 3.0 behavior.
+
+## 59. Decision table
+
+| Requirement | Prefer |
+|---|---|
+| one labeled column | `Series` |
+| labeled table | `DataFrame` |
+| label-based selection | `.loc` |
+| position-based selection | `.iloc` |
+| conditional row filter | boolean mask + `.loc` |
+| conditional update | one `.loc[...] = ...` assignment |
+| parse numeric text | `pd.to_numeric()` |
+| parse datetime text | `pd.to_datetime()` / `parse_dates` |
+| inspect missing values | `isna()` |
+| remove rows under a defined missing-data rule | `dropna()` |
+| fill missing values under a defined rule | `fillna()` |
+| aggregate by group | `groupby()` + `agg()` |
+| group statistic beside each row | `groupby()` + `transform()` |
+| database-style key join | `merge()` + `validate=` when known |
+| stack compatible tables | `concat()` |
+| summary matrix | `pivot_table()` |
+| wide-to-long reshape | `melt()` |
+| load CSV | `read_csv()` |
+| save CSV | `to_csv(index=...)` |
+
+## 60. Quick reference
+
+```text
+import pandas as pd
+
+pd.Series(...)
+pd.DataFrame(...)
+
+df.shape
+df.columns
+df.dtypes
+df.head()
+df.info()
+df.describe()
+
+df["column"]
+df[["column_a", "column_b"]]
+df.loc[...]
+df.iloc[...]
+
+df.assign(...)
+df.rename(...)
+df.drop(...)
+df.sort_values(...)
+df.sort_index(...)
+
+df.isna()
+df.dropna(...)
+df.fillna(...)
+df.astype(...)
+pd.to_numeric(...)
+pd.to_datetime(...)
+
+series.str...
+series.dt...
+series.value_counts()
+series.nunique()
+
+df.groupby(...)
+df.agg(...)
+df.transform(...)
+
+df.merge(...)
+pd.concat(...)
+df.pivot_table(...)
+df.melt(...)
+
+pd.read_csv(...)
+df.to_csv(...)
+```
+
+## 61. Design checklist
+
+Before accepting a pandas transformation, ask:
+
+- What is the expected input schema?
+- Which columns are identifiers, numbers, text, dates, or categories?
+- Is the index meaningful or merely positional?
+- Could label alignment change the result?
+- Are missing values allowed, and what do they mean?
+- Is dtype inference acceptable at this boundary?
+- Are conditional updates performed directly with `.loc`?
+- Is merge cardinality known and validated?
+- Can row order vary, and should the result be sorted?
+- Is a vectorized operation available?
+- Does `apply()` or row iteration genuinely require Python-level logic?
+- Will an export accidentally include the index?
+- Are failures visible rather than silently coerced?
+- Is the pandas version contract documented?
+- Does the code rely on pre-pandas-3.0 copy/view assumptions?
+
+## 62. Exercise
+
+Build a fictional order-analysis pipeline:
+
+1. Create or load a CSV with `order_id`, `customer_id`, `date`, `status`, `category`, and `amount`.
+2. Preserve identifiers as strings if leading zeros are allowed.
+3. Parse `date` as datetime.
+4. Validate required columns before transforming data.
+5. Parse `amount` numerically and detect invalid entries.
+6. Report missing values by column.
+7. Keep only `paid` rows without a Python row loop.
+8. Create a derived `month` column from the datetime values.
+9. Produce a grouped summary by `category` with total, mean, and transaction count.
+10. Join the orders to a fictional customer table and validate the expected merge cardinality.
+11. Sort report output explicitly.
+12. Save the final summary without exporting an accidental index.
+13. Make expected data-quality failures visible rather than swallowing them.
+
+Extension challenges:
+
+- compare a vectorized solution with an `apply()` solution;
+- build a wide pivot table;
+- convert it back to long form with `melt()`;
+- add tests for row counts, totals, key uniqueness, dtypes, and merge cardinality;
+- document which transformations change the row count and why.
+
+## 63. Connections to earlier Python concepts
+
+`pandas` builds on concepts already studied:
+
+- **lists and dictionaries:** constructors and result conversions;
+- **functions:** reusable transformation steps;
+- **boolean logic:** row masks;
+- **exceptions:** visible I/O, conversion, and join failures;
+- **files and context managers:** CSV and other data boundaries;
+- **`pathlib`:** path objects work naturally with pandas I/O;
+- **`datetime`:** pandas extends date/time work to columns;
+- **CSV and JSON:** pandas adds a table-oriented layer over data formats;
+- **`decimal`:** representation choices still matter; floating-point columns do not replace exact-decimal domain requirements;
+- **`logging`:** operational pipelines should report useful context without hiding exceptions;
+- **`os` and `shutil`:** filesystem discovery and movement often surround a pandas transformation pipeline.
+
+## 64. References
+
+Primary references used for this chapter:
+
+- [pandas 3.0.5 documentation](https://pandas.pydata.org/docs/)
+- [pandas User Guide](https://pandas.pydata.org/docs/user_guide/)
+- [pandas package overview](https://pandas.pydata.org/docs/getting_started/overview.html)
+- [Getting started tutorials](https://pandas.pydata.org/docs/getting_started/intro_tutorials/)
+- [Copy-on-Write](https://pandas.pydata.org/docs/user_guide/copy_on_write.html)
+- [pandas 3.0.0 release notes](https://pandas.pydata.org/docs/whatsnew/v3.0.0.html)
+
+The official documentation identifies pandas 3.0.5 as the current stable documentation used for this chapter, and pandas 3.0 requires Python 3.11 or newer.
+
+## 65. Next chapter
+
+This chapter opens **Phase 9: External Libraries**.
+
+The next planned library is **`openpyxl`**, focused on programmatic Excel workbook operations.
+
+Before moving on, practice pandas with datasets small enough to inspect manually. A table library becomes useful only when you can still reason about what each transformation should do.
diff --git a/external-libraries/01-pandas/README.pt-BR.md b/external-libraries/01-pandas/README.pt-BR.md
new file mode 100644
index 0000000..7e88d73
--- /dev/null
+++ b/external-libraries/01-pandas/README.pt-BR.md
@@ -0,0 +1,1374 @@
+
+
+# Trabalhando com Dados Tabulares Usando `pandas`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Voltar para Bibliotecas Externas](../README.pt-BR.md) · [← Fase anterior: `os` + `shutil`](../../standard-library/09-os-shutil/README.pt-BR.md)
+
+A Fase 9 começa onde a biblioteca padrão termina: adicionando pacotes de terceiros com seus próprios ciclos de release, contratos de dependência e abstrações específicas de domínio.
+
+`pandas` é a primeira biblioteca externa porque se conecta diretamente a conceitos já estudados: listas, dicionários, CSV, JSON, datas, arquivos, funções, exceções, caminhos e validação de dados. O novo desafio não é apenas aprender métodos. É aprender a preservar a **semântica da tabela** enquanto as transformações se tornam mais expressivas.
+
+Este capítulo tem como alvo **pandas 3.0.x** e foi pesquisado com base na documentação oficial do pandas **3.0.5**. pandas 3.0 suporta Python 3.11 ou superior.
+
+**Tempo estimado de estudo:** 240–330 minutos.
+
+## Objetivos de aprendizagem
+
+Ao final deste capítulo, você deverá conseguir:
+
+- explicar quando pandas é mais adequado do que coleções embutidas;
+- criar e inspecionar objetos `Series` e `DataFrame`;
+- raciocinar sobre índices, labels, alinhamento, colunas e dtypes;
+- selecionar linhas e colunas com colchetes, `.loc` e `.iloc`;
+- construir máscaras booleanas e atualizar linhas com segurança;
+- entender Copy-on-Write no pandas 3.0 e por que chained assignment não é válido;
+- tratar valores ausentes com uma política explícita;
+- converter colunas numéricas, textuais e de data/hora de forma deliberada;
+- agregar com `groupby()`, `agg()` e `transform()`;
+- combinar tabelas com `merge()` validado e `concat()`;
+- remodelar dados com `pivot_table()` e `melt()`;
+- carregar e salvar CSV com decisões explícitas de schema;
+- preferir operações vetorizadas quando elas expressam o problema;
+- reconhecer quando `apply()` e iteração por linhas não são bons padrões iniciais;
+- construir pipelines de dados tabulares determinísticos e revisáveis.
+
+## 1. Por que `pandas` existe
+
+`pandas` é uma biblioteca de terceiros para dados rotulados e tabulares. Ela é especialmente útil quando os dados possuem linhas, colunas, rótulos, valores ausentes, tipos diferentes por coluna ou precisam de filtros, agrupamentos, joins, reshape e entrada/saída por arquivos.
+
+Ela não substitui as coleções do Python. Uma lista ou um dicionário frequentemente é melhor para pequenos estados de uma aplicação. `pandas` ganha força quando o problema é principalmente uma tabela de dados e as operações se aplicam a colunas ou grupos de linhas.
+
+## 2. Bibliotecas externas introduzem contratos de dependência
+
+Ao contrário da biblioteca padrão, pandas precisa ser instalado no ambiente Python que executará o código. O repositório declara as dependências executáveis da Fase 9 em `requirements-external.txt`.
+
+Um contrato de dependência responde perguntas como:
+
+```text
+Qual pacote é necessário?
+Quais versões são suportadas pelo capítulo?
+Quais versões do Python são suportadas pelo pacote?
+Como o CI reproduz o mesmo ambiente?
+Quais comportamentos mudaram entre versões principais?
+```
+
+Este capítulo mira deliberadamente pandas 3.0.x em vez de fingir que todas as versões históricas se comportam da mesma forma.
+
+## 3. Instale pandas em um ambiente isolado
+
+Um ambiente virtual mantém as dependências do projeto separadas de outras instalações Python.
+
+```bash
+python -m venv .venv
+```
+
+Ative-o conforme seu sistema operacional e instale o contrato de dependências do repositório:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+A documentação oficial também mostra instalação direta com `pip install pandas` e via conda-forge. Aqui, um arquivo de dependências é preferível porque torna o contrato executável do guia reproduzível.
+
+## 4. Importe pandas com o alias convencional
+
+A documentação e a comunidade pandas usam `pd`:
+
+```python
+import pandas as pd
+```
+
+Seguir essa convenção facilita comparar os exemplos com a documentação oficial e outros projetos.
+
+## 5. `Series` modela uma dimensão rotulada
+
+Uma `Series` é uma estrutura unidimensional rotulada. Ela combina valores com um índice. Uma coluna de um DataFrame normalmente é exposta como uma `Series`.
+
+```python
+import pandas as pd
+
+
+scores = pd.Series([8.5, 9.0, 7.5], index=["A", "B", "C"])
+print(scores.loc["B"])
+```
+
+```text
+9.0
+```
+
+Uma `Series` não é apenas uma lista com mais métodos. Os rótulos participam de seleção e alinhamento.
+
+## 6. `DataFrame` modela uma tabela rotulada
+
+Um `DataFrame` é uma tabela bidimensional com linhas e colunas rotuladas. Colunas diferentes podem ter dtypes diferentes, o que o torna adequado para muitos conjuntos semelhantes a planilhas, SQL e CSV.
+
+```python
+import pandas as pd
+
+
+people = pd.DataFrame(
+ {
+ "name": ["Ana", "Bruno"],
+ "age": [28, 34],
+ "active": [True, False],
+ }
+)
+print(people.shape)
+```
+
+```text
+(2, 3)
+```
+
+Um dicionário de sequências com o mesmo tamanho é um dos construtores mais claros para exemplos pequenos. As chaves se tornam nomes de colunas.
+
+## 7. O índice faz parte do modelo de dados
+
+O índice rotula as linhas. O `RangeIndex` padrão é perfeitamente adequado em muitos casos. Use um índice personalizado significativo apenas quando os rótulos das linhas realmente participarem da seleção, alinhamento ou identidade.
+
+```python
+import pandas as pd
+
+
+temperatures = pd.Series([21.5, 19.0], index=["morning", "evening"])
+print(temperatures.index.tolist())
+```
+
+```text
+['morning', 'evening']
+```
+
+Não transforme automaticamente todo identificador de negócio em índice. Uma coluna normal costuma ser mais fácil de validar, combinar, exportar e explicar.
+
+## 8. O alinhamento por rótulos é poderoso e pode surpreender
+
+Quando pandas combina objetos rotulados, ele normalmente alinha valores pelos rótulos do índice em vez de usar cegamente a posição física.
+
+```python
+import pandas as pd
+
+
+left = pd.Series([10, 20], index=["a", "b"])
+right = pd.Series([1, 2], index=["b", "c"])
+print((left + right).to_dict())
+```
+
+O rótulo compartilhado `b` recebe valores dos dois objetos. Rótulos presentes em apenas um dos lados se tornam ausentes no resultado.
+
+Trate o índice como dado, não decoração. Rótulos inesperados podem alterar aritmética, joins, atribuições e comparações.
+
+## 9. Inspecione colunas e dtypes cedo
+
+Um fluxo confiável inspeciona o que foi carregado antes de transformar. `columns` revela os rótulos e `dtypes` mostra o dtype escolhido para cada coluna.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"label": ["x", "y"], "count": [1, 2]})
+print(table.columns.tolist())
+print(table.dtypes.astype(str).to_dict())
+```
+
+pandas 3.0 mudou um padrão importante: colunas contendo somente strings são inferidas com o dtype dedicado `str`, em vez do antigo dtype genérico `object`.
+
+Esse é um dos motivos para este capítulo declarar explicitamente sua versão de pandas.
+
+## 10. `shape`, `size` e `ndim` respondem perguntas diferentes
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+print(table.shape, table.size, table.ndim)
+```
+
+```text
+(3, 2) 6 2
+```
+
+- `shape` retorna `(linhas, colunas)`;
+- `size` retorna a quantidade de células;
+- `ndim` retorna o número de dimensões.
+
+São fatos estruturais, não validações por si só.
+
+## 11. Visualizar dados ajuda, mas não valida
+
+`head()` e `tail()` são ferramentas rápidas de inspeção. `sample()` pode mostrar padrões fora das primeiras linhas, mas use `random_state` quando a reprodução do resultado for importante.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [10, 20, 30, 40]})
+print(table.sample(2, random_state=7)["value"].tolist())
+```
+
+Uma visualização não prova que colunas obrigatórias existem, dtypes estão corretos, identificadores são únicos ou valores estão dentro de intervalos permitidos.
+
+## 12. `info()` e `describe()` respondem perguntas de inspeção diferentes
+
+`DataFrame.info()` resume quantidade de linhas, nomes de colunas, contagem de não nulos, dtypes e uso aproximado de memória. É útil para inspeção humana.
+
+`describe()` resume estatísticas como contagem, média, dispersão e extremos para colunas apropriadas.
+
+```python
+import pandas as pd
+
+
+values = pd.DataFrame({"amount": [10.0, 20.0, 30.0]})
+print(values["amount"].describe()[["count", "mean", "max"]].to_dict())
+```
+
+Nenhuma dessas funções entende o significado de negócio. Um valor negativo pode ser matematicamente válido e inválido para um conjunto específico. Um identificador pode parecer numérico e ainda não fazer sentido calcular sua média.
+
+## 13. Selecione uma coluna com colchetes
+
+`df["column"]` retorna uma `Series`.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"unit price": [10.0, 12.5]})
+prices = table["unit price"]
+print(type(prices).__name__)
+```
+
+```text
+Series
+```
+
+Prefira colchetes a acesso por atributo como `df.column`. Nomes podem conter espaços, conflitar com atributos do DataFrame ou ser escolhidos dinamicamente.
+
+## 14. Selecione múltiplas colunas com uma lista
+
+Passar uma lista de nomes de colunas retorna um DataFrame e preserva a ordem solicitada.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"a": [1], "b": [2], "c": [3]})
+subset = table[["c", "a"]]
+print(subset.columns.tolist())
+```
+
+```text
+['c', 'a']
+```
+
+A distinção importa: uma string seleciona uma coluna como `Series`; uma lista de strings seleciona uma tabela como `DataFrame`.
+
+## 15. Use `.loc` para seleção baseada em rótulos
+
+`.loc` seleciona por labels e condições booleanas.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"status": ["new", "done"], "value": [5, 8]}, index=["a", "b"])
+print(table.loc["b", "value"])
+```
+
+```text
+8
+```
+
+`.loc` também é a ferramenta preferida para atribuição condicional porque linhas-alvo e coluna de destino podem ser expressas numa única operação.
+
+## 16. Use `.iloc` para seleção posicional
+
+`.iloc` seleciona por posição inteira, independentemente dos labels do índice.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"name": ["first", "second", "third"]}, index=[10, 20, 30])
+print(table.iloc[1, 0])
+```
+
+```text
+second
+```
+
+Use `.iloc` quando a posição for parte do significado. Não use apenas porque a seleção por labels parece menos familiar.
+
+## 17. Slices por label e por posição têm limites diferentes
+
+Com `.loc`, um slice por label inclui o label final quando ele existe. Com `.iloc`, o fatiamento segue o Python posicional normal e exclui a posição final.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [10, 20, 30]}, index=["a", "b", "c"])
+print(table.loc["a":"b", "value"].tolist())
+print(table.iloc[0:2, 0].tolist())
+```
+
+```text
+[10, 20]
+[10, 20]
+```
+
+Os exemplos retornam os mesmos valores por motivos diferentes. Mantenha os dois modelos mentais separados.
+
+## 18. Máscaras booleanas filtram linhas
+
+Uma comparação contra uma `Series` produz uma `Series` booleana. Usar essa máscara com `.loc` mantém as linhas em que a condição é verdadeira.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"amount": [50, 120, 80]})
+mask = orders["amount"] >= 80
+print(orders.loc[mask, "amount"].tolist())
+```
+
+```text
+[120, 80]
+```
+
+Máscaras são uma das pontes mais importantes entre lógica booleana do Python e operações orientadas a tabelas.
+
+## 19. Combine máscaras com `&`, `|` e `~`
+
+Use operadores booleanos elemento a elemento para condições de `Series` e coloque cada comparação entre parênteses.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {"status": ["paid", "paid", "pending"], "amount": [50, 150, 200]}
+)
+mask = (orders["status"] == "paid") & (orders["amount"] >= 100)
+print(orders.loc[mask, "amount"].tolist())
+```
+
+```text
+[150]
+```
+
+Os operadores escalares `and` e `or` do Python não expressam lógica linha a linha para uma `Series` pandas.
+
+## 20. Faça atribuição no objeto que pretende alterar
+
+Ao atualizar um DataFrame, expresse o seletor de linhas e a coluna de destino em uma única operação `.loc`.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"amount": [50, 150], "priority": ["normal", "normal"]})
+orders.loc[orders["amount"] >= 100, "priority"] = "high"
+print(orders["priority"].tolist())
+```
+
+```text
+['normal', 'high']
+```
+
+Esse padrão é explícito e compatível com a semântica Copy-on-Write do pandas 3.0.
+
+## 21. Copy-on-Write é a regra no pandas 3.0
+
+No pandas 3.0, objetos derivados por indexação ou métodos se comportam como cópias do ponto de vista do usuário. Alterar um objeto derivado não altera o objeto original.
+
+```python
+import pandas as pd
+
+
+original = pd.DataFrame({"value": [1, 2, 3]})
+subset = original["value"]
+subset.iloc[0] = 99
+
+print(original["value"].tolist())
+print(subset.tolist())
+```
+
+```text
+[1, 2, 3]
+[99, 2, 3]
+```
+
+Internamente, pandas pode compartilhar memória até que uma escrita exija uma cópia. Para o código de aplicação, o contrato importante é o comportamento observável.
+
+## 22. Chained assignment não é uma estratégia válida de atualização
+
+Código como este usa múltiplas etapas de indexação:
+
+```text
+df["value"][mask] = 10
+```
+
+No pandas 3.0, chained assignment não atualiza o DataFrame original. A antiga ambiguidade que gerava `SettingWithCopyWarning` foi substituída por uma regra mais simples: modifique o próprio objeto em uma operação.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [1, 2, 3]})
+table.loc[table["value"] >= 2, "value"] = 10
+print(table["value"].tolist())
+```
+
+```text
+[1, 10, 10]
+```
+
+Esse é um ponto importante ao encontrar materiais antigos de pandas 1.x ou 2.x na internet.
+
+## 23. Crie colunas derivadas com expressões vetorizadas
+
+Expressões de coluna operam sobre objetos `Series` inteiros e normalmente são mais claras do que escrever um loop Python para cada linha.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"units": [2, 3], "unit_price": [10.0, 12.5]})
+sales["total"] = sales["units"] * sales["unit_price"]
+print(sales["total"].tolist())
+```
+
+```text
+[20.0, 37.5]
+```
+
+Esse é um hábito central do pandas: expresse a transformação em termos de colunas quando a própria regra é orientada a colunas.
+
+## 24. `assign()` é útil em method chains
+
+`assign()` retorna um DataFrame com colunas adicionadas ou substituídas.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"units": [2, 3], "price": [5.0, 8.0]})
+result = sales.assign(total=lambda frame: frame["units"] * frame["price"])
+print(result["total"].tolist())
+```
+
+```text
+[10.0, 24.0]
+```
+
+Use quando um pipeline fica mais legível mantendo as transformações encadeadas. A atribuição direta continua válida quando é mais clara.
+
+## 25. Renomeie, remova e ordene com intenção
+
+`rename()` pode normalizar nomes externos ruins. `drop()` remove linhas ou colunas. `sort_values()` e `sort_index()` tornam a ordem explícita.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"Order Amount": [20, 10], "temporary_note": ["b", "a"]})
+clean = (
+ table.rename(columns={"Order Amount": "amount"})
+ .drop(columns=["temporary_note"])
+ .sort_values("amount")
+)
+print(clean["amount"].tolist())
+```
+
+```text
+[10, 20]
+```
+
+Um campo removido pode ser impossível de reconstruir depois. Uma ordenação pode ser necessária para relatórios determinísticos. Essas operações codificam política, não apenas aparência.
+
+## 26. Dados ausentes precisam de uma política explícita
+
+Valores ausentes podem significar desconhecido, não aplicável, não coletado, inválido, atrasado ou intencionalmente vazio. Esses significados não são equivalentes.
+
+Antes de chamar `dropna()` ou `fillna()`, decida o significado da ausência para cada coluna relevante.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"value": [1.0, None, 3.0], "label": ["a", "b", None]})
+print(table.isna().sum().to_dict())
+```
+
+```text
+{'value': 1, 'label': 1}
+```
+
+Contar ausências é observação. Removê-las ou preenchê-las é uma transformação que exige regra.
+
+## 27. `dropna()` descarta observações
+
+`dropna()` só é correto quando as observações afetadas são realmente descartáveis segundo o contrato de dados.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"id": [1, 2, 3], "amount": [10.0, None, 30.0]})
+complete = table.dropna(subset=["amount"])
+print(complete["id"].tolist())
+```
+
+```text
+[1, 3]
+```
+
+Usar `dropna()` sem `subset` pode remover linhas por causa de campos irrelevantes para a operação atual.
+
+## 28. `fillna()` insere um significado escolhido
+
+Substituir um valor desconhecido por zero afirma que zero é a interpretação correta.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"discount": [0.1, None, 0.2]})
+filled = table["discount"].fillna(0.0)
+print(filled.tolist())
+```
+
+```text
+[0.1, 0.0, 0.2]
+```
+
+Documente regras de preenchimento porque elas alteram o conjunto de dados, não apenas sua aparência.
+
+## 29. Dtypes fazem parte do schema
+
+Uma coluna que parece numérica pode ter sido carregada como texto. Uma data pode continuar sendo string. Um identificador pode precisar permanecer textual mesmo quando todos os valores possuem apenas dígitos.
+
+Use `astype()` quando os valores já forem válidos para o dtype de destino:
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"units": ["1", "2", "3"]})
+table["units"] = table["units"].astype("int64")
+print(table["units"].sum())
+```
+
+```text
+6
+```
+
+Escolha tipos de acordo com significado e operações, não apenas aparência.
+
+## 30. `to_numeric()` torna a política de parsing explícita
+
+`pd.to_numeric()` é útil quando a conversão numérica pode falhar.
+
+```python
+import pandas as pd
+
+
+raw = pd.Series(["10", "invalid", "30"])
+parsed = pd.to_numeric(raw, errors="coerce")
+print(parsed.isna().sum())
+```
+
+```text
+1
+```
+
+`errors="coerce"` transforma entradas inválidas em valores ausentes. Isso só é seguro quando o fluxo depois audita e trata essas novas ausências.
+
+## 31. Operações de string são vetorizadas sob `.str`
+
+O accessor `.str` aplica operações de string a uma `Series`.
+
+```python
+import pandas as pd
+
+
+names = pd.Series([" Alpha ", "BETA "])
+normalized = names.str.strip().str.lower()
+print(normalized.tolist())
+```
+
+```text
+['alpha', 'beta']
+```
+
+Normalize texto apenas quando a normalização corresponder ao contrato do domínio. Colocar identificadores em minúsculas ou remover espaços pode mudar significado.
+
+## 32. Converta datetimes antes de usar semântica de data/hora
+
+Use `pd.to_datetime()` quando texto deve se tornar realmente data/hora.
+
+```python
+import pandas as pd
+
+
+dates = pd.to_datetime(pd.Series(["2026-08-01", "2026-08-03"]), format="%Y-%m-%d")
+print((dates.iloc[1] - dates.iloc[0]).days)
+```
+
+```text
+2
+```
+
+O accessor `.dt` então expõe componentes vetorizados:
+
+```python
+import pandas as pd
+
+
+dates = pd.to_datetime(pd.Series(["2026-01-15", "2026-02-20"]))
+print(dates.dt.month.tolist())
+```
+
+```text
+[1, 2]
+```
+
+Formatos ambíguos de data devem ser controlados explicitamente em vez de adivinhados.
+
+## 33. Duplicatas precisam de uma definição
+
+Duas linhas são duplicadas apenas em relação às colunas escolhidas. `duplicated()` e `drop_duplicates()` aceitam `subset` para expressar a chave real de unicidade.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame(
+ {"id": [1, 1, 2], "note": ["first", "repeated", "other"]}
+)
+print(table.duplicated(subset=["id"]).tolist())
+```
+
+```text
+[False, True, False]
+```
+
+Não deduplique linhas inteiras quando a regra verdadeira é unicidade por identificador.
+
+## 34. Métodos de frequência e resumo são diagnósticos compactos
+
+`value_counts()` mostra frequência de categorias. `nunique()` conta valores distintos não ausentes por padrão. Reduções como `sum()`, `mean()`, `min()`, `max()` e `count()` resumem colunas.
+
+```python
+import pandas as pd
+
+
+statuses = pd.Series(["paid", "pending", "paid", "paid"])
+print(statuses.value_counts().sort_index().to_dict())
+```
+
+```text
+{'paid': 3, 'pending': 1}
+```
+
+Uma frequência é evidência sobre os dados observados, não prova de que toda categoria observada é permitida.
+
+## 35. `groupby()` implementa split-apply-combine
+
+`groupby()` separa linhas por uma ou mais chaves, aplica agregação ou transformação e combina os resultados.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {"category": ["A", "B", "A"], "amount": [10, 20, 30]}
+)
+summary = sales.groupby("category")["amount"].sum()
+print(summary.to_dict())
+```
+
+```text
+{'A': 40, 'B': 20}
+```
+
+Agrupamento é central no pandas porque muitas perguntas analíticas são "calcule algo por categoria, cliente, data, região ou outra chave".
+
+## 36. Agregação nomeada torna o schema de saída explícito
+
+Named aggregation permite declarar tanto a coluna de origem quanto a operação.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {"category": ["A", "A", "B"], "amount": [10.0, 30.0, 20.0]}
+)
+summary = sales.groupby("category", as_index=False).agg(
+ total=("amount", "sum"),
+ average=("amount", "mean"),
+)
+print(summary.to_dict(orient="records"))
+```
+
+```text
+[{'category': 'A', 'total': 40.0, 'average': 20.0}, {'category': 'B', 'total': 20.0, 'average': 20.0}]
+```
+
+Um schema de saída estável facilita validação, exportação e testes posteriores.
+
+## 37. `transform()` mantém resultados alinhados às linhas originais
+
+Ao contrário de uma agregação normal, `transform()` retorna resultado alinhado ao índice original.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame({"team": ["A", "A", "B"], "score": [10, 20, 30]})
+sales["team_total"] = sales.groupby("team")["score"].transform("sum")
+print(sales["team_total"].tolist())
+```
+
+```text
+[30, 30, 30]
+```
+
+É útil quando uma estatística de grupo precisa permanecer ao lado de cada observação.
+
+## 38. `merge()` combina tabelas por chaves
+
+`merge()` é a operação de join estilo banco de dados do pandas.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"customer_id": [1, 2], "amount": [10, 20]})
+customers = pd.DataFrame({"customer_id": [1, 2], "name": ["A", "B"]})
+result = orders.merge(customers, on="customer_id", how="left")
+print(result["name"].tolist())
+```
+
+```text
+['A', 'B']
+```
+
+Um merge que executa sem erro ainda pode estar logicamente errado se as chaves tiverem duplicatas inesperadas.
+
+## 39. Valide a cardinalidade do merge
+
+O argumento `validate` pode afirmar relações como `one_to_one`, `one_to_many`, `many_to_one` ou `many_to_many`.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame({"customer_id": [1, 1], "amount": [10, 20]})
+customers = pd.DataFrame({"customer_id": [1], "name": ["A"]})
+result = orders.merge(
+ customers,
+ on="customer_id",
+ how="left",
+ validate="many_to_one",
+)
+print(len(result))
+```
+
+```text
+2
+```
+
+Quando a cardinalidade faz parte do contrato, validá-la transforma duplicação acidental de chaves em falha visível, em vez de multiplicação silenciosa de linhas.
+
+## 40. `concat()` empilha objetos compatíveis
+
+`pd.concat()` combina objetos pandas ao longo de um eixo. Concatenar linhas é comum quando vários arquivos têm o mesmo schema.
+
+```python
+import pandas as pd
+
+
+first = pd.DataFrame({"id": [1, 2]})
+second = pd.DataFrame({"id": [3]})
+combined = pd.concat([first, second], ignore_index=True)
+print(combined["id"].tolist())
+```
+
+```text
+[1, 2, 3]
+```
+
+Depois da concatenação, decida se os labels de índice originais devem ser preservados ou redefinidos.
+
+## 41. `pivot_table()` resume em uma matriz
+
+Uma pivot table agrupa dados por dimensões de linha e coluna e agrega valores.
+
+```python
+import pandas as pd
+
+
+sales = pd.DataFrame(
+ {
+ "region": ["north", "north", "south"],
+ "product": ["A", "B", "A"],
+ "amount": [10, 20, 30],
+ }
+)
+pivot = sales.pivot_table(
+ index="region",
+ columns="product",
+ values="amount",
+ aggfunc="sum",
+ fill_value=0,
+)
+print(pivot.loc["north", "B"])
+```
+
+```text
+20
+```
+
+Use pivot table quando a saída desejada for ela própria uma matriz de resumo.
+
+## 42. `melt()` converte dados wide para long
+
+Dados em formato long frequentemente facilitam agrupamento e visualização.
+
+```python
+import pandas as pd
+
+
+wide = pd.DataFrame({"item": ["A"], "jan": [10], "feb": [20]})
+long = wide.melt(id_vars="item", var_name="month", value_name="amount")
+print(long.to_dict(orient="records"))
+```
+
+```text
+[{'item': 'A', 'month': 'jan', 'amount': 10}, {'item': 'A', 'month': 'feb', 'amount': 20}]
+```
+
+`melt()` é especialmente útil quando colunas repetidas representam, na verdade, valores de uma mesma variável conceitual.
+
+## 43. `read_csv()` transforma texto delimitado em DataFrame
+
+`pd.read_csv()` é uma das funções de I/O mais importantes do pandas.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+source = StringIO("id,amount\n1,10.5\n2,20.0\n")
+table = pd.read_csv(source)
+print(table.shape)
+```
+
+```text
+(2, 2)
+```
+
+pandas infere um schema a menos que você forneça instruções mais fortes. Inferência é conveniência, não contrato de negócio.
+
+## 44. Controle o parsing de CSV quando o schema é conhecido
+
+Argumentos úteis de `read_csv()` incluem `usecols`, `dtype`, `parse_dates`, `na_values`, `encoding` e configurações de delimitador.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+source = StringIO("code,date,amount\n001,2026-08-01,10.5\n")
+table = pd.read_csv(
+ source,
+ dtype={"code": "str"},
+ parse_dates=["date"],
+)
+print(table.loc[0, "code"])
+print(table.loc[0, "date"].year)
+```
+
+```text
+001
+2026
+```
+
+Fornecer informações conhecidas de schema reduz inferências acidentais e documenta expectativas perto da fronteira de entrada.
+
+## 45. Identificadores frequentemente pertencem ao dtype string
+
+Um código como `00123` pode parecer numérico, mas não ter significado aritmético. Convertê-lo para inteiro destrói zeros à esquerda.
+
+```python
+import pandas as pd
+
+
+codes = pd.Series(["001", "010"], dtype="str")
+print(codes.tolist())
+```
+
+```text
+['001', '010']
+```
+
+Modele identificadores segundo sua semântica, não pelos caracteres que contêm.
+
+## 46. `to_csv()` deve tornar a política do índice explícita
+
+Para tabelas comuns cujo índice é apenas um label interno de linha, `index=False` evita que uma coluna extra apareça ao reimportar.
+
+```python
+from io import StringIO
+
+import pandas as pd
+
+
+table = pd.DataFrame({"id": [1], "value": [10]})
+buffer = StringIO()
+table.to_csv(buffer, index=False)
+print(buffer.getvalue().strip())
+```
+
+```text
+id,value
+1,10
+```
+
+Se o índice carrega informação real, exporte-o intencionalmente em vez de sempre desativá-lo.
+
+## 47. Method chains tornam a ordem das transformações visível
+
+Uma cadeia curta pode ser lida como pipeline: filtrar, derivar, ordenar, agrupar, exportar.
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {"status": ["paid", "pending", "paid"], "amount": [30, 50, 20]}
+)
+result = (
+ orders.loc[orders["status"] == "paid"]
+ .assign(taxed=lambda frame: frame["amount"] * 1.1)
+ .sort_values("amount")
+)
+print(result["amount"].tolist())
+```
+
+```text
+[20, 30]
+```
+
+Cadeias longas podem ficar difíceis de depurar. Divida em etapas nomeadas quando a intenção deixar de ser óbvia.
+
+## 48. Prefira operações vetorizadas a loops Python por linha
+
+Quando um cálculo puder ser expresso com aritmética de `Series`, comparações, `.str`, `.dt` ou reduções nativas, prefira essa forma.
+
+```python
+import pandas as pd
+
+
+table = pd.DataFrame({"quantity": [2, 3], "price": [4.0, 5.0]})
+table["total"] = table["quantity"] * table["price"]
+print(table["total"].tolist())
+```
+
+```text
+[8.0, 15.0]
+```
+
+Vetorização comunica a intenção tabular e normalmente permite que pandas/NumPy executem o trabalho com mais eficiência do que chamadas Python repetidas.
+
+## 49. `apply()` não é automaticamente vetorização
+
+`Series.apply()` e `DataFrame.apply()` por linha podem ser úteis para lógica Python customizada, mas podem executar uma função Python repetidamente.
+
+Antes de usar `apply()`, verifique se pandas já fornece uma operação nativa para a transformação.
+
+Use `apply()` porque a lógica customizada é realmente necessária, não apenas porque parece menor que um loop.
+
+## 50. Evite `iterrows()` para transformações comuns
+
+Iteração por linha às vezes é necessária em fronteiras com efeitos externos, mas filtros, cálculos, agregações e atribuições normalmente têm formas melhores orientadas a colunas.
+
+Uma linha retornada por `iterrows()` é uma representação `Series`. Não a trate como um handle mutável para atualizar o DataFrame original.
+
+## 51. `.copy()` ainda tem papel deliberado
+
+Copy-on-Write significa que cópias defensivas não são mais necessárias apenas para silenciar o antigo `SettingWithCopyWarning`.
+
+Use `.copy()` quando uma cópia independente e imediata fizer parte do design ou contrato de ciclo de vida.
+
+```python
+import pandas as pd
+
+
+original = pd.DataFrame({"value": [1, 2]})
+independent = original.copy()
+independent.loc[0, "value"] = 99
+print(original["value"].tolist())
+```
+
+```text
+[1, 2]
+```
+
+## 52. Erros do DataFrame devem permanecer visíveis
+
+Falhas comuns incluem:
+
+```text
+KeyError
+ValueError
+pandas.errors.ParserError
+pandas.errors.MergeError
+```
+
+Não capture exceções amplas apenas para manter um pipeline em movimento. Uma tabela parcialmente transformada pode ser mais perigosa do que uma falha visível.
+
+Falhas de validação devem interromper o fluxo quando continuar tornaria a saída não confiável.
+
+## 53. Exemplo prático: construir uma pequena tabela de vendas
+
+```python
+import pandas as pd
+
+
+data = {
+ "product": ["Notebook", "Keyboard", "Mouse"],
+ "units": [2, 5, 8],
+ "unit_price": [3500.0, 180.0, 95.0],
+}
+
+sales = pd.DataFrame(data)
+sales["total"] = sales["units"] * sales["unit_price"]
+
+print(f"shape: {sales.shape}")
+print(f"columns: {sales.columns.tolist()}")
+print(f"grand total: {sales['total'].sum():.2f}")
+```
+
+```text
+shape: (3, 4)
+columns: ['product', 'units', 'unit_price', 'total']
+grand total: 8660.00
+```
+
+Este exemplo espelha `examples/dataframe_basics.py` e demonstra construção, inspeção, coluna derivada e agregação.
+
+## 54. Exemplo prático: filtrar e atribuir com segurança
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [101, 102, 103, 104],
+ "status": ["paid", "pending", "paid", "paid"],
+ "amount": [120.0, 80.0, 250.0, 90.0],
+ }
+)
+
+orders["priority"] = "normal"
+orders.loc[
+ (orders["status"] == "paid") & (orders["amount"] >= 200),
+ "priority",
+] = "high"
+
+selected = orders.loc[
+ orders["status"] == "paid",
+ ["order_id", "priority"],
+]
+print(selected.to_dict(orient="records"))
+```
+
+```text
+[{'order_id': 101, 'priority': 'normal'}, {'order_id': 103, 'priority': 'high'}, {'order_id': 104, 'priority': 'normal'}]
+```
+
+A atualização acontece diretamente em `orders` por `.loc`, o padrão seguro para pandas 3.0.
+
+## 55. Exemplo prático: resumo agrupado
+
+```python
+import pandas as pd
+
+
+transactions = pd.DataFrame(
+ {
+ "category": ["books", "games", "books", "games", "office"],
+ "amount": [40.0, 120.0, 35.0, 80.0, 25.0],
+ }
+)
+
+summary = (
+ transactions.groupby("category", as_index=False)
+ .agg(
+ total_amount=("amount", "sum"),
+ transaction_count=("amount", "size"),
+ )
+ .sort_values("category")
+)
+
+print(summary.to_dict(orient="records"))
+```
+
+As colunas nomeadas de saída formam um schema estável. O sort final torna o exemplo determinístico.
+
+## 56. Exemplo prático: merge validado
+
+```python
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [1, 2, 3],
+ "customer_id": [10, 20, 10],
+ "amount": [50.0, 80.0, 30.0],
+ }
+)
+customers = pd.DataFrame(
+ {
+ "customer_id": [10, 20],
+ "customer": ["Aster", "Boreal"],
+ }
+)
+
+report = orders.merge(
+ customers,
+ on="customer_id",
+ how="left",
+ validate="many_to_one",
+)
+report = report[["order_id", "customer", "amount"]].sort_values("order_id")
+
+print(report.to_dict(orient="records"))
+```
+
+`validate="many_to_one"` documenta que muitos pedidos podem apontar para um cliente, enquanto a tabela de clientes deve manter chaves únicas.
+
+## 57. Exemplo prático: pipeline CSV determinístico
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pandas as pd
+
+
+with TemporaryDirectory() as temp_dir:
+ workspace = Path(temp_dir)
+ source = workspace / "orders.csv"
+ destination = workspace / "paid_orders.csv"
+
+ source.write_text(
+ "order_id,date,status,amount\n"
+ "1,2026-08-01,paid,120.50\n"
+ "2,2026-08-02,pending,80.00\n"
+ "3,2026-08-03,paid,250.00\n",
+ encoding="utf-8",
+ )
+
+ orders = pd.read_csv(source, parse_dates=["date"])
+ paid_orders = orders.loc[orders["status"] == "paid"].sort_values("order_id")
+ paid_orders.to_csv(destination, index=False)
+
+ print(f"rows: {len(paid_orders)}")
+ print(f"total: {paid_orders['amount'].sum():.2f}")
+ print(f"saved: {destination.name}")
+```
+
+```text
+rows: 2
+total: 370.50
+saved: paid_orders.csv
+```
+
+O diretório temporário mantém o exemplo seguro, `parse_dates` estabelece semântica de datetime na entrada, o sort estabiliza o resultado e `index=False` mantém o schema do CSV deliberado.
+
+## 58. Erros comuns
+
+Evite estes padrões:
+
+- tratar pandas como substituto para toda lista ou dicionário;
+- confiar em dtypes inferidos sem inspeção;
+- converter identificadores para números só porque contêm dígitos;
+- usar chained assignment em vez de uma atualização `.loc` única;
+- chamar `dropna()` ou `fillna()` sem definir o significado de ausência;
+- juntar tabelas sem verificar unicidade ou cardinalidade das chaves;
+- depender de ordem incidental das linhas;
+- usar `iterrows()` para cálculos que possuem formas vetorizadas;
+- usar `apply()` antes de verificar operações nativas do pandas;
+- exportar um índice interno sem querer;
+- esconder erros de parsing ou merge e continuar com dados parciais;
+- copiar conselhos de pandas 1.x/2.x sem verificar o comportamento do pandas 3.0.
+
+## 59. Tabela de decisão
+
+| Necessidade | Prefira |
+|---|---|
+| uma coluna rotulada | `Series` |
+| tabela rotulada | `DataFrame` |
+| seleção por label | `.loc` |
+| seleção por posição | `.iloc` |
+| filtro condicional de linhas | máscara booleana + `.loc` |
+| atualização condicional | uma atribuição `.loc[...] = ...` |
+| converter texto numérico | `pd.to_numeric()` |
+| converter texto de data/hora | `pd.to_datetime()` / `parse_dates` |
+| inspecionar ausências | `isna()` |
+| remover linhas sob regra definida de ausência | `dropna()` |
+| preencher ausências sob regra definida | `fillna()` |
+| agregar por grupo | `groupby()` + `agg()` |
+| estatística de grupo ao lado de cada linha | `groupby()` + `transform()` |
+| join por chave estilo banco | `merge()` + `validate=` quando conhecido |
+| empilhar tabelas compatíveis | `concat()` |
+| matriz de resumo | `pivot_table()` |
+| reshape wide-to-long | `melt()` |
+| carregar CSV | `read_csv()` |
+| salvar CSV | `to_csv(index=...)` |
+
+## 60. Referência rápida
+
+```text
+import pandas as pd
+
+pd.Series(...)
+pd.DataFrame(...)
+
+df.shape
+df.columns
+df.dtypes
+df.head()
+df.info()
+df.describe()
+
+df["column"]
+df[["column_a", "column_b"]]
+df.loc[...]
+df.iloc[...]
+
+df.assign(...)
+df.rename(...)
+df.drop(...)
+df.sort_values(...)
+df.sort_index(...)
+
+df.isna()
+df.dropna(...)
+df.fillna(...)
+df.astype(...)
+pd.to_numeric(...)
+pd.to_datetime(...)
+
+series.str...
+series.dt...
+series.value_counts()
+series.nunique()
+
+df.groupby(...)
+df.agg(...)
+df.transform(...)
+
+df.merge(...)
+pd.concat(...)
+df.pivot_table(...)
+df.melt(...)
+
+pd.read_csv(...)
+df.to_csv(...)
+```
+
+## 61. Checklist de design
+
+Antes de aceitar uma transformação pandas, pergunte:
+
+- Qual é o schema esperado de entrada?
+- Quais colunas são identificadores, números, texto, datas ou categorias?
+- O índice é significativo ou apenas posicional?
+- O alinhamento por labels pode alterar o resultado?
+- Valores ausentes são permitidos e o que significam?
+- Inferência de dtype é aceitável nessa fronteira?
+- Atualizações condicionais são feitas diretamente com `.loc`?
+- A cardinalidade do merge é conhecida e validada?
+- A ordem das linhas pode variar, e a saída precisa ser ordenada?
+- Existe operação vetorizada disponível?
+- `apply()` ou iteração por linhas realmente exigem lógica Python?
+- Uma exportação incluirá o índice sem querer?
+- Falhas permanecem visíveis em vez de serem silenciosamente convertidas?
+- O contrato de versão do pandas está documentado?
+- O código depende de suposições antigas de copy/view anteriores ao pandas 3.0?
+
+## 62. Exercício
+
+Construa um pipeline fictício de análise de pedidos:
+
+1. Crie ou carregue um CSV com `order_id`, `customer_id`, `date`, `status`, `category` e `amount`.
+2. Preserve identificadores como strings se zeros à esquerda forem permitidos.
+3. Converta `date` para datetime.
+4. Valide as colunas obrigatórias antes das transformações.
+5. Converta `amount` numericamente e detecte entradas inválidas.
+6. Relate valores ausentes por coluna.
+7. Mantenha apenas linhas `paid` sem loop Python por linha.
+8. Crie uma coluna derivada `month` a partir dos datetimes.
+9. Gere resumo por `category` com total, média e quantidade de transações.
+10. Faça join com uma tabela fictícia de clientes e valide a cardinalidade esperada.
+11. Ordene explicitamente a saída do relatório.
+12. Salve o resumo final sem exportar um índice acidental.
+13. Torne falhas esperadas de qualidade visíveis em vez de escondê-las.
+
+Desafios de extensão:
+
+- compare uma solução vetorizada com uma baseada em `apply()`;
+- construa uma pivot table wide;
+- converta-a novamente para long com `melt()`;
+- adicione testes para contagem de linhas, totais, unicidade de chaves, dtypes e cardinalidade de merge;
+- documente quais transformações alteram a quantidade de linhas e por quê.
+
+## 63. Conexões com conceitos anteriores de Python
+
+`pandas` constrói sobre conceitos já estudados:
+
+- **listas e dicionários:** construtores e conversões de resultados;
+- **funções:** etapas reutilizáveis de transformação;
+- **lógica booleana:** máscaras de linhas;
+- **exceções:** falhas visíveis de I/O, conversão e joins;
+- **arquivos e context managers:** fronteiras CSV e outros dados;
+- **`pathlib`:** objetos de caminho funcionam naturalmente com I/O do pandas;
+- **`datetime`:** pandas amplia o trabalho com datas para colunas;
+- **CSV e JSON:** pandas adiciona uma camada orientada a tabelas sobre formatos de dados;
+- **`decimal`:** escolhas de representação continuam importantes; colunas de ponto flutuante não substituem requisitos de decimal exato;
+- **`logging`:** pipelines operacionais devem relatar contexto útil sem esconder exceções;
+- **`os` e `shutil`:** descoberta e movimentação de arquivos frequentemente cercam pipelines pandas.
+
+## 64. Referências
+
+Principais referências usadas neste capítulo:
+
+- [Documentação pandas 3.0.5](https://pandas.pydata.org/docs/)
+- [Pandas User Guide](https://pandas.pydata.org/docs/user_guide/)
+- [Visão geral do pacote pandas](https://pandas.pydata.org/docs/getting_started/overview.html)
+- [Tutoriais Getting Started](https://pandas.pydata.org/docs/getting_started/intro_tutorials/)
+- [Copy-on-Write](https://pandas.pydata.org/docs/user_guide/copy_on_write.html)
+- [Release notes do pandas 3.0.0](https://pandas.pydata.org/docs/whatsnew/v3.0.0.html)
+
+A documentação oficial identifica pandas 3.0.5 como a documentação estável usada para este capítulo, e pandas 3.0 requer Python 3.11 ou superior.
+
+## 65. Próximo capítulo
+
+Este capítulo abre a **Fase 9: Bibliotecas Externas**.
+
+A próxima biblioteca planejada é **`openpyxl`**, com foco em operações programáticas sobre workbooks do Excel.
+
+Antes de avançar, pratique pandas com conjuntos de dados pequenos o suficiente para serem inspecionados manualmente. Uma biblioteca de tabelas só se torna útil quando você ainda consegue raciocinar sobre o que cada transformação deveria fazer.
diff --git a/external-libraries/01-pandas/examples/csv_pipeline.py b/external-libraries/01-pandas/examples/csv_pipeline.py
new file mode 100644
index 0000000..a430bbb
--- /dev/null
+++ b/external-libraries/01-pandas/examples/csv_pipeline.py
@@ -0,0 +1,26 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pandas as pd
+
+
+with TemporaryDirectory() as temp_dir:
+ workspace = Path(temp_dir)
+ source = workspace / "orders.csv"
+ destination = workspace / "paid_orders.csv"
+
+ source.write_text(
+ "order_id,date,status,amount\n"
+ "1,2026-08-01,paid,120.50\n"
+ "2,2026-08-02,pending,80.00\n"
+ "3,2026-08-03,paid,250.00\n",
+ encoding="utf-8",
+ )
+
+ orders = pd.read_csv(source, parse_dates=["date"])
+ paid_orders = orders.loc[orders["status"] == "paid"].sort_values("order_id")
+ paid_orders.to_csv(destination, index=False)
+
+ print(f"rows: {len(paid_orders)}")
+ print(f"total: {paid_orders['amount'].sum():.2f}")
+ print(f"saved: {destination.name}")
diff --git a/external-libraries/01-pandas/examples/dataframe_basics.py b/external-libraries/01-pandas/examples/dataframe_basics.py
new file mode 100644
index 0000000..ac0306f
--- /dev/null
+++ b/external-libraries/01-pandas/examples/dataframe_basics.py
@@ -0,0 +1,15 @@
+import pandas as pd
+
+
+data = {
+ "product": ["Notebook", "Keyboard", "Mouse"],
+ "units": [2, 5, 8],
+ "unit_price": [3500.0, 180.0, 95.0],
+}
+
+sales = pd.DataFrame(data)
+sales["total"] = sales["units"] * sales["unit_price"]
+
+print(f"shape: {sales.shape}")
+print(f"columns: {sales.columns.tolist()}")
+print(f"grand total: {sales['total'].sum():.2f}")
diff --git a/external-libraries/01-pandas/examples/filter_and_assign.py b/external-libraries/01-pandas/examples/filter_and_assign.py
new file mode 100644
index 0000000..122e4f5
--- /dev/null
+++ b/external-libraries/01-pandas/examples/filter_and_assign.py
@@ -0,0 +1,16 @@
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [101, 102, 103, 104],
+ "status": ["paid", "pending", "paid", "paid"],
+ "amount": [120.0, 80.0, 250.0, 90.0],
+ }
+)
+
+orders["priority"] = "normal"
+orders.loc[(orders["status"] == "paid") & (orders["amount"] >= 200), "priority"] = "high"
+
+selected = orders.loc[orders["status"] == "paid", ["order_id", "priority"]]
+print(selected.to_dict(orient="records"))
diff --git a/external-libraries/01-pandas/examples/groupby_summary.py b/external-libraries/01-pandas/examples/groupby_summary.py
new file mode 100644
index 0000000..f2b4188
--- /dev/null
+++ b/external-libraries/01-pandas/examples/groupby_summary.py
@@ -0,0 +1,17 @@
+import pandas as pd
+
+
+transactions = pd.DataFrame(
+ {
+ "category": ["books", "games", "books", "games", "office"],
+ "amount": [40.0, 120.0, 35.0, 80.0, 25.0],
+ }
+)
+
+summary = (
+ transactions.groupby("category", as_index=False)
+ .agg(total_amount=("amount", "sum"), transaction_count=("amount", "size"))
+ .sort_values("category")
+)
+
+print(summary.to_dict(orient="records"))
diff --git a/external-libraries/01-pandas/examples/merge_tables.py b/external-libraries/01-pandas/examples/merge_tables.py
new file mode 100644
index 0000000..2c77e93
--- /dev/null
+++ b/external-libraries/01-pandas/examples/merge_tables.py
@@ -0,0 +1,21 @@
+import pandas as pd
+
+
+orders = pd.DataFrame(
+ {
+ "order_id": [1, 2, 3],
+ "customer_id": [10, 20, 10],
+ "amount": [50.0, 80.0, 30.0],
+ }
+)
+customers = pd.DataFrame(
+ {
+ "customer_id": [10, 20],
+ "customer": ["Aster", "Boreal"],
+ }
+)
+
+report = orders.merge(customers, on="customer_id", how="left", validate="many_to_one")
+report = report[["order_id", "customer", "amount"]].sort_values("order_id")
+
+print(report.to_dict(orient="records"))
diff --git a/external-libraries/README.es.md b/external-libraries/README.es.md
new file mode 100644
index 0000000..3e6cb30
--- /dev/null
+++ b/external-libraries/README.es.md
@@ -0,0 +1,36 @@
+
+
+# Fase 9: Bibliotecas Externas
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Volver al repositorio](../docs/localized/README.es.md)
+
+La Fase 9 introduce paquetes de terceros después de completar las bases del lenguaje Python y de la biblioteca estándar.
+
+Las bibliotecas externas añaden una nueva responsabilidad de ingeniería: **contratos de dependencias**. Un programa pasa a depender no solo de Python, sino también de versiones de paquetes, estado de instalación, notas de versión y límites de compatibilidad.
+
+## Estado
+
+> 🚧 **En progreso**
+
+## Ruta de aprendizaje
+
+1. ✅ [`pandas`: Trabajando con Datos Tabulares](01-pandas/README.es.md)
+2. ⏳ `openpyxl`: automatización de libros de Excel
+3. ⏳ `requests`: clientes HTTP y consumo de APIs
+4. ⏳ `pytest`: pruebas automatizadas
+
+## Contrato de dependencias
+
+Los ejemplos ejecutables publicados en esta fase usan las dependencias declaradas en [`requirements-external.txt`](../requirements-external.txt). El CI del repositorio instala ese archivo antes de ejecutar los ejemplos aprobados de bibliotecas externas.
+
+El capítulo de pandas apunta a **pandas 3.0.x**. pandas 3.0 es compatible con Python 3.11+, mientras este repositorio continúa validando los ejemplos en Python 3.13.
+
+## Por qué esta fase llega ahora
+
+Las fases anteriores establecieron colecciones, funciones, errores, archivos, módulos, CSV/JSON, fechas, rutas, logging, iteración, aritmética decimal y contratos de filesystem. Las bibliotecas externas deben construir sobre esas habilidades, no sustituirlas.
+
+El siguiente capítulo planificado es **`openpyxl`**.
diff --git a/external-libraries/README.md b/external-libraries/README.md
index 7ab7372..4638655 100644
--- a/external-libraries/README.md
+++ b/external-libraries/README.md
@@ -1,5 +1,36 @@
-# External Libraries
+
-Guides to third-party Python packages such as `pandas`, `openpyxl`, `requests`, and `pytest` will live here.
+# Phase 9: External Libraries
-> Status: planned.
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Back to repository](../README.md)
+
+Phase 9 introduces third-party packages after the Python language and standard-library foundations are complete.
+
+External libraries add a new engineering responsibility: **dependency contracts**. A program now depends not only on Python, but also on package versions, installation state, release notes, and compatibility boundaries.
+
+## Status
+
+> 🚧 **In progress**
+
+## Learning path
+
+1. ✅ [`pandas`: Working with Tabular Data](01-pandas/README.md)
+2. ⏳ `openpyxl`: Excel workbook automation
+3. ⏳ `requests`: HTTP clients and API consumption
+4. ⏳ `pytest`: automated testing
+
+## Dependency contract
+
+Published executable examples from this phase use the dependencies declared in [`requirements-external.txt`](../requirements-external.txt). Repository CI installs that file before executing approved external-library examples.
+
+The pandas chapter targets **pandas 3.0.x**. pandas 3.0 supports Python 3.11+, while this repository continues to validate examples on Python 3.13.
+
+## Why this phase comes now
+
+The earlier phases established collections, functions, errors, files, modules, CSV/JSON, dates, paths, logging, iteration, decimal arithmetic, and filesystem contracts. External libraries should build on those skills rather than replace them.
+
+The next planned chapter is **`openpyxl`**.
diff --git a/external-libraries/README.pt-BR.md b/external-libraries/README.pt-BR.md
new file mode 100644
index 0000000..9e38175
--- /dev/null
+++ b/external-libraries/README.pt-BR.md
@@ -0,0 +1,36 @@
+
+
+# Fase 9: Bibliotecas Externas
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Voltar ao repositório](../docs/localized/README.pt-BR.md)
+
+A Fase 9 introduz pacotes de terceiros depois que as bases da linguagem Python e da biblioteca padrão estão concluídas.
+
+Bibliotecas externas acrescentam uma nova responsabilidade de engenharia: **contratos de dependência**. Um programa passa a depender não apenas do Python, mas também de versões de pacotes, estado de instalação, release notes e limites de compatibilidade.
+
+## Status
+
+> 🚧 **Em andamento**
+
+## Trilha de aprendizagem
+
+1. ✅ [`pandas`: Trabalhando com Dados Tabulares](01-pandas/README.pt-BR.md)
+2. ⏳ `openpyxl`: automação de workbooks do Excel
+3. ⏳ `requests`: clientes HTTP e consumo de APIs
+4. ⏳ `pytest`: testes automatizados
+
+## Contrato de dependências
+
+Os exemplos executáveis publicados nesta fase usam as dependências declaradas em [`requirements-external.txt`](../requirements-external.txt). O CI do repositório instala esse arquivo antes de executar os exemplos aprovados de bibliotecas externas.
+
+O capítulo de pandas tem como alvo **pandas 3.0.x**. O pandas 3.0 suporta Python 3.11+, enquanto este repositório continua validando os exemplos em Python 3.13.
+
+## Por que esta fase vem agora
+
+As fases anteriores estabeleceram coleções, funções, erros, arquivos, módulos, CSV/JSON, datas, caminhos, logging, iteração, aritmética decimal e contratos de filesystem. Bibliotecas externas devem construir sobre essas habilidades, não substituí-las.
+
+O próximo capítulo planejado é **`openpyxl`**.
diff --git a/requirements-external.txt b/requirements-external.txt
new file mode 100644
index 0000000..be344ad
--- /dev/null
+++ b/requirements-external.txt
@@ -0,0 +1,3 @@
+# External-library dependencies used by published executable examples.
+# Keep version ranges aligned with the documented curriculum contracts.
+pandas>=3.0,<3.1
diff --git a/scripts/example_manifest.txt b/scripts/example_manifest.txt
index 48cdb0d..9fa8568 100644
--- a/scripts/example_manifest.txt
+++ b/scripts/example_manifest.txt
@@ -1,6 +1,6 @@
# Repository-relative Python files approved for unattended execution.
# Add an example only when it is deterministic, non-interactive, network-free,
-# free of external dependencies, and safe to run in a public CI environment.
+# limited to declared repository dependencies, and safe to run in public CI.
comments-and-documentation/01-comments/examples/business_rule_comments.py
comments-and-documentation/01-comments/examples/unnecessary_comments.py
@@ -158,3 +158,8 @@ standard-library/09-os-shutil/examples/copy_tree_and_move.py
standard-library/09-os-shutil/examples/environment_contract.py
standard-library/09-os-shutil/examples/scan_directory.py
standard-library/09-os-shutil/examples/walk_with_pruning.py
+external-libraries/01-pandas/examples/csv_pipeline.py
+external-libraries/01-pandas/examples/dataframe_basics.py
+external-libraries/01-pandas/examples/filter_and_assign.py
+external-libraries/01-pandas/examples/groupby_summary.py
+external-libraries/01-pandas/examples/merge_tables.py
diff --git a/standard-library/09-os-shutil/README.es.md b/standard-library/09-os-shutil/README.es.md
index 3fac378..fcc4c2c 100644
--- a/standard-library/09-os-shutil/README.es.md
+++ b/standard-library/09-os-shutil/README.es.md
@@ -1414,4 +1414,4 @@ Este capítulo cierra la **Fase 8: Standard Library**.
La fase comenzó con modelado de rutas orientado a objetos en `pathlib` y avanzó por contratos de fecha y hora, formatos estructurados de datos, logging, colecciones especializadas, iteración lazy, aritmética decimal y finalmente la propia frontera del sistema operativo.
-La siguiente fase planificada es la **Fase 9: Bibliotecas Externas**, comenzando por `pandas` cuando esa fase esté disponible.
+Continúa con la **Fase 9: Bibliotecas Externas**: [`pandas` — Trabajando con Datos Tabulares](../../external-libraries/01-pandas/README.es.md).
diff --git a/standard-library/09-os-shutil/README.md b/standard-library/09-os-shutil/README.md
index 3358c53..9fd85cc 100644
--- a/standard-library/09-os-shutil/README.md
+++ b/standard-library/09-os-shutil/README.md
@@ -1414,4 +1414,4 @@ This chapter closes **Phase 8: Standard Library**.
The phase started with object-oriented path modeling in `pathlib` and progressed through date/time contracts, structured data formats, logging, specialized collections, lazy iteration, decimal arithmetic, and finally the operating-system boundary itself.
-The next planned phase is **Phase 9: External Libraries**, beginning with `pandas` when that phase becomes available.
+Continue with **Phase 9: External Libraries**: [`pandas` — Working with Tabular Data](../../external-libraries/01-pandas/README.md).
diff --git a/standard-library/09-os-shutil/README.pt-BR.md b/standard-library/09-os-shutil/README.pt-BR.md
index 5119324..86882b1 100644
--- a/standard-library/09-os-shutil/README.pt-BR.md
+++ b/standard-library/09-os-shutil/README.pt-BR.md
@@ -1414,4 +1414,4 @@ Este capítulo encerra a **Fase 8: Standard Library**.
A fase começou com modelagem orientada a objetos em `pathlib` e avançou por contratos de data e hora, formatos estruturados de dados, logging, coleções especializadas, iteração lazy, aritmética decimal e finalmente a própria fronteira do sistema operacional.
-A próxima fase planejada é a **Fase 9: Bibliotecas Externas**, começando por `pandas` quando essa fase estiver disponível.
+Continue com a **Fase 9: Bibliotecas Externas**: [`pandas` — Trabalhando com Dados Tabulares](../../external-libraries/01-pandas/README.pt-BR.md).