estados = pd.read_csv("dados/estados.csv")
estados.dtypesestado object
populacao int64
taxa_homicidios float64
sigla object
dtype: object
Todo modelo dos capítulos anteriores recebeu números prontos: um vetor de entrada, um alvo, um gradiente que decrescia passo a passo. Dado bruto não chega assim. Chega em uma tabela, com uma coluna que conta pessoas, outra que mede uma taxa, outra que nomeia um lugar — e cada uma pede um tratamento diferente antes de qualquer conta começar. A primeira pergunta, antes de qualquer ajuste, é de que tipo é cada coluna.
Dado estruturado se divide em duas famílias. Numérico é o que se mede: contínuo é qualquer valor dentro de um intervalo — uma taxa, um comprimento, um preço —, discreto é contagem, sempre inteiro — número de filhos, de vendas, de acessos. Categórico é o que se rotula: nominal não tem ordem — uma cor, a sigla de um estado, o nome de uma cidade —, ordinal tem ordem mas não tem distância — uma faixa “baixa/média/alta” diz que alta vem depois de média, mas não diz quanto maior —, e binário é o caso particular de só dois valores, do tipo “sim ou não”.
| Família | Subtipo | Tem ordem? | Tem distância? | Exemplo |
|---|---|---|---|---|
| Numérico | Contínuo | sim | sim | uma taxa, um preço |
| Numérico | Discreto | sim | sim | uma contagem |
| Categórico | Nominal | não | não | uma sigla, uma cor |
| Categórico | Ordinal | sim | não | uma faixa “baixa/média/alta” |
| Categórico | Binário | — | não | “sim”/“não” |
A distância é o que separa numérico de ordinal: em ambos dá para dizer “isto vem depois daquilo”, mas só no numérico faz sentido perguntar “quanto depois”.
O tipo de uma coluna decide qual gráfico faz sentido para ela, qual estatística pode ser calculada sobre ela e como um programa consegue validar o que entra nela; tratar uma variável ordinal como se fosse numérica produz conclusão errada com aparência de rigor — a média entre “baixa” e “alta” devolve um número, só que um número que não significa nada, porque a distância entre as duas nunca foi definida.
pandas infere sozinhoPara ver a distinção aplicada a um caso concreto, considere as unidades federativas do Brasil, com população e taxa de homicídios:
estado object
populacao int64
taxa_homicidios float64
sigla object
dtype: object
O pandas acerta metade da tarefa sozinho, só olhando a forma dos valores: populacao vira int64 — é contagem, portanto numérico discreto —, e taxa_homicidios vira float64 — é uma taxa, portanto numérico contínuo. estado e sigla viram object, o tipo genérico de texto que o pandas usa quando não sabe o que mais dizer sobre uma coluna: ele enxerga strings, não que “RO” e “AC” vêm de um conjunto fechado de rótulos possíveis. É inferência sobre a forma do valor, não sobre o que ele significa — só quem lê o dado sabe que sigla é categórico nominal.
As 27 linhas trazem 27 siglas distintas — nenhuma se repete.
Dizer ao pandas o que já se sabe sobre a coluna é uma linha:
Index(['AC', 'AL', 'AM', 'AP', 'BA', 'CE', 'DF', 'ES', 'GO', 'MA', 'MG', 'MS',
'MT', 'PA', 'PB', 'PE', 'PI', 'PR', 'RJ', 'RN', 'RO', 'RR', 'RS', 'SC',
'SE', 'SP', 'TO'],
dtype='object')
Antes de acreditar que a conversão economiza alguma coisa, meça. Comparando o consumo de memória coluna a coluna, de antes para depois:
| object | category | variação (%) | |
|---|---|---|---|
| Index | 132 | 132 | 0.0 |
| estado | 1753 | 1753 | 0.0 |
| populacao | 216 | 216 | 0.0 |
| taxa_homicidios | 216 | 216 | 0.0 |
| sigla | 1377 | 2476 | 79.8 |
| total | 3694 | 4793 | 29.8 |
sigla sobe de 1.377 para 2.476 bytes — quase 80% a mais —, e como nenhuma outra coluna muda, o DataFrame inteiro sobe de 3.694 para 4.793 bytes, quase 30% a mais. Com 27 valores todos distintos, não há repetição nenhuma para o category amortizar: o array de códigos mais o índice de categorias, por cima do que já existia, custam mais do que os ponteiros de texto do object. O ganho aqui é semântico, não de memória — e, sem repetição para amortizar, nem podia ser: o que muda é que operações que só fazem sentido sobre um conjunto fechado de rótulos passam a existir, como listar as categorias possíveis ou recusar um valor que não está entre elas. A economia de memória aparece quando poucos valores se repetem em muitas linhas — é o caso que a seção 6.3 mostra, ao tipar uma coluna de cidade.
Nem todo categórico é nominal. Uma faixa de população tem ordem — “6 a 15 milhões” vem depois de “2 a 6 milhões” — mesmo sem ter distância: a faixa não diz o quanto maior. pd.cut corta um numérico em faixas categóricas, e ordered=True guarda essa ordem em vez de descartá-la:
populacao
2 a 6 milhões 10
6 a 15 milhões 9
até 2 milhões 5
mais de 15 milhões 3
Name: count, dtype: int64
Dez estados ficam na faixa intermediária, de 2 a 6 milhões; só três passam de 15 milhões. Com a ordem declarada, comparar deixa de ser comparar texto e passa a ser comparar posição:
Quinze estados ficam abaixo da faixa “6 a 15 milhões” — o < olha a posição de cada faixa na ordem que ordered=True fixou, não a ordem alfabética dos rótulos (que poria “6 a 15 milhões” antes de “até 2 milhões”, e a conta sairia errada).
Pedir a mesma comparação a uma coluna nominal não tem resposta, e o pandas recusa a pergunta em vez de inventar uma:
Unordered Categoricals can only compare equality or not
Sem ordem declarada, “menor que” não está definido para sigla — é a mesma pergunta que devolveria um número, e uma falsa sensação de precisão, se a coluna tivesse ficado como texto solto em vez de category.
A mesma distinção decide o gráfico. Um categórico com um número por rótulo pede barras — a pergunta é “quanto vale cada um”; um numérico contínuo pede histograma — a pergunta é “como os valores se distribuem”:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.5))
top10 = estados.nlargest(10, "populacao").sort_values("populacao")
ax1.barh(top10["estado"], top10["populacao"] / 1e6)
ax1.set_xlabel("população (milhões)")
ax1.set_title("10 maiores populações")
ax2.hist(estados["taxa_homicidios"], bins=8)
ax2.set_xlabel("taxa de homicídios (por 100 mil hab.)")
ax2.set_ylabel("estados")
ax2.set_title("distribuição da taxa de homicídios")
plt.tight_layout()
plt.show()
estados ainda é só quatro colunas soltas, cada uma com o seu tipo. A próxima seção olha a estrutura que as mantém juntas — o DataFrame em si, a sua forma, o seu índice, e as duas formas de escolher linha e coluna dentro dele.