Pandas: Operações com Strings
Última atualização: 2026-08-26
Os textos em dados do mundo real nunca são limpos — espaços extras, capitalização inconsistente, caracteres especiais, formatos misturados. O acessador .str do Pandas traz vetorização para operações com strings: limpe uma coluna inteira de texto em uma única linha de código, até 100 vezes mais rápido que um laço em Python. Esta seção aborda os métodos de string mais comumente usados e mostra como construir pipelines de limpeza encadeados.
1. What You Will Learn
- ❶ The .str accessor
- ❷ Common methods (strip/lower/upper/title/replace)
- ❸ Regular expressions
- ❹ split / extract
- ❺ contains / startswith / match
2. Limpeza de Entrada do Usuário de Carol
(1) O Problema: Entradas de Usuário Vêm em Todos os Formatos
Os dados do usuário que Carol coletou estão uma bagunça:
import pandas as pd
df = pd.DataFrame({
'name': [' alice ', 'BOB', 'Charlie!', ' carol ', 'DaVid123'],
'email': ['alice@EXAMPLE.com', 'bob@company.ORG', 'charlie@uni.edu',
'CAROL@example.COM', 'david@test.net'],
'phone': ['555-0100', '(555) 0200', '555.0300', '+1-555-0400', '5550500']
})
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
(2) A Solução: Limpeza Encadeada com .str
▶ Exemplo: Limpeza de Texto Encadeada (Dificuldade ⭐)
import pandas as pd
df = pd.DataFrame({
'name': [' alice ', 'BOB', 'Charlie!', ' carol ', 'DaVid123'],
'email': ['alice@EXAMPLE.com', 'bob@company.ORG', 'charlie@uni.edu',
'CAROL@example.COM', 'david@test.net']
})
# Encadeia métodos .str: strip → replace → title
df['name_clean'] = (df['name']
.str.strip()
.str.replace(r'[^a-zA-Z ]', '', regex=True)
.str.title()
)
print(df[['name', 'name_clean']])
# name name_clean
# 0 alice Alice
# 1 BOB Bob
# 2 Charlie! Charlie
# 3 carol Carol
# 4 DaVid123 David
# Coloca o domínio do email em minúsculas
df['email_clean'] = df['email'].str.lower()
print(df['email_clean'])
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
3. O Acessador .str
(1) .str vs Python str
| Característica | Python str | Pandas .str |
|---|---|---|
| Opera em | Uma única string | Uma coluna inteira (vetorizado) |
| Tratamento de NaN | Gera um erro | Ignora automaticamente (retorna NaN) |
| Encadeamento | Não suportado | ✅ Chamadas consecutivas |
| Desempenho | Loop → lento | Vetorizado → rápido |
▶ Exemplo
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
: Métodos Básicos do .str (Dificuldade ⭐)
import pandas as pd
s = pd.Series(['Hello World', 'pandas', 'DATA SCIENCE', None, 'python'])
# Conversão de caixa
print(s.str.lower()) # hello world, pandas, data science, NaN, python
print(s.str.upper()) # HELLO WORLD, PANDAS, DATA SCIENCE, NaN, PYTHON
print(s.str.title()) # Hello World, Pandas, Data Science, NaN, Python
print(s.str.capitalize()) # Hello world, Pandas, Data science, NaN, Python
# Espaços em branco
s2 = pd.Series([' hello ', '\tworld\n', ' pandas '])
print(s2.str.strip()) # hello, world, pandas
print(s2.str.lstrip()) # hello , world\n, pandas
print(s2.str.rstrip()) # hello, \tworld, pandas
# Comprimento
print(s.str.len()) # 11, 6, 12, NaN, 6
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
4. replace / contains / match
(1) replace
▶ Exemplo
> **Saída:** Execute isso em um ambiente local do Python (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e siga junto. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: Substituição de String com replace (Dificuldade ⭐⭐)
import pandas as pd
s = pd.Series(['price: $100', 'price: $200', 'cost: €50'])
# Simple replacement
print(s.str.replace('$', 'USD'))
# price: USD100, price: USD200, cost: €50
# Regex replacement
print(s.str.replace(r'[\$€]', '', regex=True))
# price: 100, price: 200, cost: 50
# Replace multiple patterns
phones = pd.Series(['555-0100', '(555) 0200', '555.0300'])
clean = phones.str.replace(r'[^0-9]', '', regex=True)
print(clean) # 5550100, 5550200, 5550300
> **Saída:** Execute isso em um ambiente local do Python (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e siga junto. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
(2) contains / startswith / endswith
▶ Exemplo
> **Saída:** Execute isso em um ambiente local do Python (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e siga junto. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: Filtragem de Strings por Condição (Dificuldade ⭐⭐)
import pandas as pd
df = pd.DataFrame({
'name': ['Alice Johnson', 'Bob Smith', 'Charlie Brown', 'Carol White'],
'email': ['alice@example.com', 'bob@company.org', 'charlie@uni.edu', 'carol@test.net']
})
# Contains substring
has_com = df[df['email'].str.contains('.com')]
print(has_com['name']) # Alice Johnson
# Startswith / endswith
starts_with_a = df[df['name'].str.startswith('A')]
print(starts_with_a)
edu_email = df[df['email'].str.endswith('.edu')]
print(edu_email['name']) # Charlie Brown
# Regex contains — find org or edu domains
org_or_edu = df[df['email'].str.contains(r'\.(org|edu)$', regex=True)]
print(org_or_edu[['name', 'email']])
> **Saída:** Execute isso em um ambiente local do Python (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e siga junto. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
(3) contains vs match
| Método | Posição de correspondência | Regex |
|---|---|---|
| contains | Qualquer lugar na string | ✅ |
| match | Do início | ✅ |
| startswith | Do início | ❌ (apenas string simples) |
| endswith | Do final | ❌ (apenas string simples) |
5. divisão / extração
(1) divisão
▶ Exemplo
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: Dividindo Strings com split (Dificuldade ⭐⭐)
import pandas as pd
df = pd.DataFrame({
'name': ['Alice Johnson', 'Bob Smith', 'Charlie Brown'],
'email': ['alice@example.com', 'bob@company.org', 'charlie@uni.edu']
})
# Dividir em lista
print(df['name'].str.split(' '))
# 0 [Alice, Johnson]
# 1 [Bob, Smith]
# 2 [Charlie, Brown]
# Dividir em colunas separadas
name_split = df['name'].str.split(' ', expand=True)
print(name_split)
# 0 1
# 0 Alice Johnson
# 1 Bob Smith
# 2 Charlie Brown
# Dividir e manter apenas a primeira parte
df['first_name'] = df['name'].str.split(' ').str[0]
print(df['first_name'])
# Dividir e-mail para obter o domínio
df['domain'] = df['email'].str.split('@').str[1]
print(df['domain'])
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
(2) extração com Regex
▶ Exemplo
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: Extraindo Padrões com extract (Dificuldade ⭐⭐⭐)
import pandas as pd
df = pd.DataFrame({
'contact': [
'Alice <alice@example.com>',
'Bob <bob@company.org>',
'Charlie (555-0100)',
'Carol <carol@test.net>'
]
})
# Extrair e-mail com grupo regex
df['email'] = df['contact'].str.extract(r'<(.+?)>')
print(df[['contact', 'email']])
# contact email
# 0 Alice <alice@example.com> alice@example.com
# 1 Bob <bob@company.org> bob@company.org
# 2 Charlie (555-0100) NaN
# 3 Carol <carol@test.net> carol@test.net
# Extrair múltiplos grupos
df2 = pd.DataFrame({
'email': ['alice@example.com', 'bob@company.org']
})
parts = df2['email'].str.extract(r'(.+?)@(.+)')
print(parts)
# 0 1
# 0 alice example.com
# 1 bob company.org
# Extrair todas as correspondências
colors = pd.Series(['red,blue,green', 'yellow,pink'])
print(colors.str.extractall(r'(\w+)'))
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
6. Outros Métodos Comuns
(1) Referência Rápida
| Método | Finalidade | Exemplo |
|---|---|---|
| len() | Comprimento da string | s.str.len() |
| count() | Número de ocorrências de substring | s.str.count('a') |
| find() | Posição da substring | s.str.find('bc') |
| isdigit() | Todos dígitos? | s.str.isdigit() |
| isalpha() | Todas letras? | s.str.isalpha() |
| join() | Juntar com separador | s.str.join(',') |
| pad() | Preencher para largura fixa | s.str.pad(10, fillchar='0') |
| repeat() | Repetir string | s.str.repeat(3) |
| slice() | Fatiar | s.str.slice(0, 5) |
▶ Exemplo
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
: pad/slice/zfill (Dificuldade ⭐)
import pandas as pd
ids = pd.Series(['1', '23', '456', '7890'])
# Preencher com zeros até 4 dígitos
print(ids.str.zfill(4))
# 0 0001
# 1 0023
# 2 0456
# 3 7890
# Fatiar os primeiros 2 caracteres
codes = pd.Series(['US-001', 'UK-002', 'JP-003'])
print(codes.str.slice(0, 2)) # US, UK, JP
print(codes.str[:2]) # igual, sintaxe de slice do Python
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado — instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
7. Exemplo Completo: Pipeline de Limpeza de Texto de Dados de Usuário
(5) ▶ Fluxo de Trabalho de Limpeza de Texto
graph TB
A[Texto Bruto] --> B[strip: remover espaços em branco]
B --> C[lower / upper: normalizar caso]
C --> D[replace: remover caracteres especiais]
D --> E[split / extract: extrair informações]
E --> F[title: formatar saída]
F --> G[Texto Limpo]
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
▶ Exemplo
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: Limpeza de Texto Completa (Dificuldade ⭐⭐⭐)
import pandas as pd
# ============================================
# Exemplo abrangente: Pipeline de limpeza
# de texto para dados de usuário
# ============================================
# 1. Dados de usuário brutos e desorganizados
df = pd.DataFrame({
'raw_name': [' ALICE johnson ', 'bob SMITH', ' CHARLIE! brown ', 'CAROL white123'],
'raw_email': ['ALICE@Example.COM', 'bob@company.ORG', 'charlie@uni.EDU', 'CAROL@TEST.NET'],
'raw_phone': ['555-0100', '(555) 0200', '555.0300 ext 42', '+1-555-0400'],
'raw_zip': ['0123', '12345', ' 90210 ', '10001-2345']
})
# 2. Limpeza de nome: strip → remover caracteres especiais → título
df['name'] = (df['raw_name']
.str.strip()
.str.replace(r'[^a-zA-Z\s]', '', regex=True)
.str.replace(r'\s+', ' ', regex=True) # colapsar múltiplos espaços
.str.title()
)
# 3. Limpeza de email: caixa baixa
df['email'] = df['raw_email'].str.lower()
# 4. Limpeza de telefone: manter apenas dígitos
df['phone_digits'] = df['raw_phone'].str.replace(r'[^0-9]', '', regex=True)
df['phone_formatted'] = df['phone_digits'].str.slice(0, 3) + '-' + df['phone_digits'].str.slice(3, 7)
# 5. CEP: extrair código de 5 dígitos
df['zip5'] = df['raw_zip'].str.strip().str.extract(r'(\d{5})')
# 6. Domínio do email
df['domain'] = df['email'].str.split('@').str[1]
# 7. Exibir resultados limpos
print("=== Dados Limpos ===")
print(df[['name', 'email', 'phone_formatted', 'zip5', 'domain']])
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado — instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
❓ Perguntas Frequentes
P: Qual é a diferença entre .str e o str do Python? R: Os métodos str do Python operam em uma única string, enquanto o acessador .str opera em uma coluna inteira (vectorizado). O acessador .str automaticamente ignora valores NaN (retornando NaN), enquanto o str do Python lança um erro em valores NaN. O acessador .str suporta encadeamento de métodos (
s.str.strip().str.lower()), o que o str do Python não faz. Em termos de desempenho, .str é 10-100x mais rápido que loops.
P: Como os valores NaN são tratados? R: Os métodos .str automaticamente ignoram NaN — se a entrada for NaN, a saída é NaN. Não é necessário verificar manualmente a presença de valores ausentes. Esta é uma das razões pelas quais .str é mais conveniente do que usar apply + str do Python. Note que contains/match retornam False (não NaN) para valores NaN, então leve isso em consideração ao filtrar.
P: O que extract retorna? R: extract retorna um DataFrame — uma colura por grupo regex. Um único grupo resulta em um DataFrame de 1 coluna; múltiplos grupos resultam em múltiplas colunas. extractall retorna todas as correspondências (com um MultiIndex). Nota: linhas sem correspondência retornam NaN. Se precisar de uma Series, use
.str.extract(...)[0].
P: Qual é a diferença entre contains e match? R: contains corresponde a qualquer lugar na string (ex: 'abc' contains 'b' → True). match corresponde apenas a partir do início da string (ex: 'abc' match 'b' → False). startswith/endswith são comparações de string simples (sem suporte a regex). Use contains/match quando precisar de regex, e startswith/endswith para verificações de texto simples.
P: Quais são os benefícios de StringDtype? R: Por padrão, colunas de string usam o dtype objeto (objetos Python), onde cada valor é uma instância de str separada. StringDtype (
df['col'].astype('string')) é um tipo de string dedicado do Pandas que usa pd.NA para valores ausentes, é mais eficiente em memória e fornece um comportamento .str mais consistente. O Pandas 2.x recomenda StringDtype em vez de objeto para dados de string.
P: Qual é a diferença entre str.replace e df.replace? R: str.replace opera apenas em colunas de string, suporta regex (regex=True), e substitui elemento por elemento. df.replace opera em todo o DataFrame, pode substituir valores de qualquer tipo, e também suporta regex. Para substituição de string, prefira str.replace (semântica mais clara); para substituição entre colunas, use df.replace.
P: Como extrair dígitos de um número de telefone? R: Use
s.str.replace(r'[^0-9]', '', regex=True)para remover todos os caracteres não numéricos. Alternativamente, use extract:s.str.extract(r'(\d{3})[-.)\s]*(\d{3})[-.)\s]*(\d{4})')para capturar o código de área, prefixo e número da linha separadamente. Expressões regulares são a ferramenta principal para extração de texto.
📖 Resumo
- O acessor .str fornece operações de string vetorizadas e lida com NaN automaticamente
- strip/lstrip/rstrip removem espaços em branco; lower/upper/title/capitalize convertem maiúsculas/minúsculas
- replace realiza substituição (com suporte a regex); contains filtra por condição (com suporte a regex)
- split divide strings (expand=True cria colunas separadas); extract extrai correspondências de regex
- match verifica desde o início; contains verifica em qualquer lugar; startswith/endswith são apenas para texto simples
- Encadeie métodos para criar pipelines de limpeza: strip → replace → lower/title
- StringDtype substitui objeto para armazenamento de strings mais eficiente
📝 Exercícios
- Básico (Dificuldade ⭐): Crie uma Series de nomes com espaços extras e capitalização inconsistente. Limpe-a com .str.strip().str.title(), depois conte quantos nomes limpos começam com 'A'.
- Intermediário (Dificuldade ⭐⭐): Crie uma Series de números de telefone em formatos mistos. Use replace + regex para extrair apenas os dígitos, depois formate-os para 10 caracteres com zfill.
- Desafio (Dificuldade ⭐⭐⭐): Simule dados de registro de usuário (nome/email/telefone/CEP todos com problemas de formatação). Construa um pipeline de limpeza encadeado de 5 etapas — um método .str por etapa — e exiba a tabela final limpa.