alugueis = pd.read_csv("dados/alugueis.csv")
alugueis.shape(10692, 13)
A seção anterior fechou avisando que estados não tinha um valor faltante sequer, e prometeu trocar esse arquivo limpo por um que chega direto de uma fonte real. É o que este arquivo faz: um conjunto de anúncios de aluguel de imóveis em cinco cidades brasileiras, lido sem nenhuma limpeza prévia — exatamente como ele chegaria de um raspador ou de uma exportação de planilha.
10.692 imóveis, 13 colunas. head() mostra o que cada linha descreve:
| cidade | area_m2 | quartos | banheiros | vagas | andar | aceita_animal | mobiliado | condominio | aluguel | iptu | seguro_incendio | total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | São Paulo | 70 | 2 | 1 | 1 | 7 | sim | sim | 2065 | 3300 | 211 | 42 | 5618 |
| 1 | São Paulo | 320 | 4 | 4 | 0 | 20 | sim | não | 1200 | 4960 | 1750 | 63 | 7973 |
| 2 | Porto Alegre | 80 | 1 | 1 | 1 | 6 | sim | não | 1000 | 2800 | 0 | 41 | 3841 |
| 3 | Porto Alegre | 51 | 2 | 1 | 0 | 2 | sim | não | 270 | 1112 | 22 | 17 | 1421 |
| 4 | São Paulo | 25 | 1 | 1 | 0 | 1 | não | não | 0 | 800 | 25 | 11 | 836 |
Cada linha é um anúncio: a cidade, a área em metros quadrados, quantos quartos, banheiros e vagas de garagem, o andar, se aceita animal, se vem mobiliado, e cinco valores em reais — condomínio, aluguel, IPTU, seguro-incêndio e o total.
Antes de qualquer conta, dtypes — o mesmo primeiro passo da seção 6.1:
cidade object
area_m2 int64
quartos int64
banheiros int64
vagas int64
andar object
aceita_animal object
mobiliado object
condominio int64
aluguel int64
iptu int64
seguro_incendio int64
total int64
dtype: object
Doze colunas vieram como o esperado: nove numéricas (de area_m2 a vagas e de condominio a total, todas contagens ou valores em reais) e três como object porque são texto de verdade — cidade é um nome de cidade, aceita_animal e mobiliado são “sim” ou “não”. andar é a única que destoa: representa um número — o andar do imóvel —, e mesmo assim veio como object, junto dos textos.
Um número de andar deveria admitir média. Tentar calcular uma mostra o problema na prática, não em teoria:
TypeError: Could not convert string '720621-743221011224-198--1718-710511-56311108111--620-7511013212 ...
caracteres na mensagem completa: 12722
mean() nem chega a rodar: object é o tipo genérico que o pandas usa para texto, e texto não tem média. A mensagem completa, se fosse impressa inteira, traria a coluna inteira concatenada num único bloco de 12.722 caracteres — por isso o truncamento acima, e não por economia de espaço.
unique() mostra os primeiros valores distintos da coluna:
array(['7', '20', '6', '2', '1', '-', '4', '3', '10', '11'], dtype=object)
Entre '7', '20', '6' e outros números escritos como texto, aparece um '-'. Contando quantas linhas trazem exatamente esse valor:
(np.int64(2461), 23)
2.461 das 10.692 linhas — 23% do arquivo — têm "-" em vez de um número de andar. Basta uma linha assim para que a coluna inteira vire object: o pandas decide o tipo de uma coluna olhando todos os valores dela, e uma coluna não pode ser meio int64, meio texto.
pandas não pegou sozinhoO read_csv já reconhece um conjunto de marcadores de valor ausente — NaN, NA, null, n/a, campo vazio, entre outros — e os converte direto em nulo, sem que ninguém peça. "-" não está nessa lista. Para o pandas, "-" é um caractere como outro qualquer, e uma coluna com um único valor não numérico entre milhares de números vira object inteira, sem aviso.
A inferência de tipo do pandas é uma heurística sobre os caracteres do arquivo, não um contrato sobre o que a coluna significa. Ela acerta quando todo valor é uma forma reconhecível de número, texto ou marcador de nulo já catalogado — e erra silenciosamente quando um valor foge desse catálogo, mesmo sendo óbvio para quem lê a coluna que "-" ali significa “sem informação”. Conferir dtypes logo depois de ler o arquivo é o que transforma esse silêncio em alguma coisa visível antes de qualquer conta.
Quando se sabe qual é o marcador, na_values ensina o read_csv a reconhecê-lo já na leitura:
(dtype('float64'), np.int64(2461), np.float64(6.58))
Com "-" na lista de nulos, andar vira float64 — os 2.461 traços viram NaN, e a média dos demais 8.231 valores sai 6,58.
Quando não se sabe de antemão qual marcador vai aparecer, pd.to_numeric com errors="coerce" faz o trabalho na direção contrária: converte o que consegue e transforma tudo o que não é número em nulo, seja "-", seja qualquer outro caractere estranho que o arquivo trouxer.
(dtype('float64'), np.int64(2461))
O resultado bate com o de na_values neste arquivo — os mesmos 2.461 nulos —, porque "-" é o único valor fora do padrão numérico. Mas to_numeric é mais forte e mais perigoso do que parece: ele converte em silêncio qualquer valor que não reconheça, inclusive um erro de digitação que na_values=["-"] deixaria passar direto como texto visível, pronto para ser notado. Usar na_values é dizer “eu sei o que este marcador significa”; usar to_numeric(errors="coerce") é dizer “eu não sei o que vou encontrar, e prefiro perder o que não for número a travar a conversão inteira”.
category compensaA seção 6.1 converteu sigla em category e mediu que o consumo de memória piorava — 27 valores, todos distintos, sem repetição para amortizar. cidade pode ser o caso oposto, se poucos valores se repetirem por muitas linhas. Antes de converter, vale medir o consumo de memória como está:
730.981 bytes para uma coluna de texto. Quantos valores distintos existem por trás desse número:
Só cinco cidades, repetidas pelas 10.692 linhas — a proporção entre valores distintos e linhas é o inverso exato do caso de sigla. Convertendo e medindo de novo:
(730981, 11325, 2)
De 730.981 bytes como object para 11.325 bytes como category — 2% do tamanho original. Aqui a repetição é o que faz o category compensar: em vez de 10.692 ponteiros de texto, a coluna guarda cinco categorias e um array de códigos pequenos apontando para elas. É o mesmo mecanismo da 6.1, com o resultado invertido porque a proporção entre valores distintos e linhas também está invertida.
Resolvido o problema de andar, vale olhar o que a distribuição do próprio aluguel revela:
A mediana fica em R$ 2.661, e a barra mais alta do histograma mora perto dela — mas a cauda à direita se estica até R$ 45.000, um único imóvel bem separado do resto. Uma coluna corretamente tipada não garante uma distribuição bem comportada: o aluguel já é int64 desde a primeira leitura, e ainda assim traz um outlier — um valor tão distante do resto que se destaca da distribuição — que qualquer estatística resumida esconderia. É esse tipo de problema — não mais o tipo da coluna, mas o formato dos valores dentro dela — que a próxima seção enfrenta.