Ir para o conteúdo

Normalização

O módulo agrobr.normalize padroniza dados agrícolas brasileiros para garantir cruzamento entre fontes diferentes. São 40 funções organizadas em 7 sub-módulos.

Municípios IBGE

5571 municípios com código IBGE de 7 dígitos. Busca accent/case insensitive.

from agrobr.normalize import (
    buscar_municipios,
    ibge_para_municipio,
    municipio_para_ibge,
    resolver_municipio,
)

# Nome para código IBGE
municipio_para_ibge("Rondonópolis")        # 5107602
municipio_para_ibge("RONDONOPOLIS")        # 5107602 (sem acento, uppercase)
municipio_para_ibge("rondonopolis", "MT")  # 5107602 (desambiguar por UF)

# Código IBGE para info
ibge_para_municipio(5107602)
# {'codigo_ibge': 5107602, 'nome': 'Rondonópolis', 'uf': 'MT'}

# Busca parcial
buscar_municipios("sorriso", uf="MT")
# [{'codigo_ibge': 5107925, 'nome': 'Sorriso', 'uf': 'MT'}]
buscar_municipios("sorriso", uf="XX")  # InvalidParameterError, com as siglas válidas
buscar_municipios("santo", limite=-1)  # InvalidParameterError (limite negativo)

# Use o nome completo do município e a UF quando disponível
municipio_para_ibge("Brasília")                 # 5300108 (DF)
municipio_para_ibge("Brasília de Minas", "MG")  # 3108602
municipio_para_ibge(5107602)                    # InvalidParameterError: nome deve ser texto, recebeu 5107602
municipio_para_ibge("Bom Jesus")                # InvalidParameterError: ambíguo, 5 municípios (PB, PI, RN, RS, SC); informe a uf

# Nome inteiro ou código IBGE, com erro em vez de palpite
resolver_municipio("sorriso")           # {'codigo_ibge': 5107925, 'nome': 'Sorriso', 'uf': 'MT'}
resolver_municipio("5107925")           # o mesmo (código em int ou str)
resolver_municipio("Bom Jesus", "PI")   # {'codigo_ibge': 2201903, 'nome': 'Bom Jesus', 'uf': 'PI'}
resolver_municipio("Bom Jesus")         # InvalidParameterError: ambíguo, lista os 5 e pede a uf
resolver_municipio("Santa Rita", "MG")  # InvalidParameterError: não encontrado, lista os candidatos

resolver_municipio é a regra do parâmetro municipio das funções que filtram por município: o nome casa por inteiro, sem caixa, acento e espaços repetidos, e nunca por pedaço ("Santa Rita" não é "Santa Rita do Sapucaí"). Nome de mais de um município, nome inexistente, código fora do cadastro ou município de outra UF levantam InvalidParameterError com os candidatos.

Grafias anteriores que as fontes ainda publicam levam ao município atual: "Açu" (Assú/RN), "Arês" (Arez/RN), "São Luiz" (São Luiz do Anauá/RR), "Fortaleza do Tabocão" (Tabocão/TO), "Santo Antônio do Leverger" (Santo Antônio de Leverger/MT), "Muquém de São Francisco" (Muquém do São Francisco/BA) e "Poxoréo" (Poxoréu/MT), conforme as Alterações Toponímicas Municipais do IBGE. Acento agudo (´), crase (`) e aspas tipográficas (’, ‘) no lugar do apóstrofo contam como ': "Conquista D´Oeste", como a FUNAI publica, é Conquista D'Oeste/MT.

ibge_para_municipio, buscar_municipios, coordenada_para_municipio e resolver_municipio devolvem cópias: alterar o dicionário devolvido não muda a consulta seguinte.

Dados da API IBGE Localidades — livre para uso.

Cruzar por município

Os datasets municipais trazem a coluna cod_municipio (Int64, o código IBGE de 7 dígitos), igual em todos, e o join sai sem conversão:

from agrobr import datasets

pam = await datasets.producao_anual("soja", ano=2023, nivel="municipio", uf="MT")
zarc = await datasets.zoneamento_agricola(produto="soja", uf="MT")
cruzado = pam.merge(zarc, on="cod_municipio")

Onde a linha não é de município, cod_municipio é nulo: UF e Brasil das pesquisas do IBGE, o fallback da CONAB (por UF) e a apólice do PSR sem código. As colunas de antes ficam:

Dataset Coluna de antes Tipo cod_municipio
producao_anual, pecuaria_municipal, extrativismo_vegetal, silvicultura, censo_agropecuario, censo_agropecuario_historico, censo_agropecuario_legado localidade_cod int, em qualquer nível só nas linhas de município
cadastro_rural cod_municipio_ibge int o mesmo valor
queimadas municipio_id int o mesmo valor
desmatamento (DETER) municipio_id texto em inteiro
seguro_rural cd_ibge texto em inteiro; nulo sem código
uso_do_solo (municipal) geocodigo texto em inteiro; nulo quando o código não tem o prefixo de uma UF
zoneamento_agricola geocodigo texto em inteiro

O censo_agropecuario_municipal_1985 fica fora: os municípios de 1985 não correspondem 1:1 aos códigos atuais, e o nome vem como lido. precos_diesel e movimentacao_portuaria trazem só o nome do município; para cruzar, use municipio_para_ibge(nome, uf) (acima) e confira os nomes que não casarem.

Geocodificação Reversa

Lookup (lat, lon) → município via centroide mais próximo. Zero HTTP, sub-ms. 5571 municípios com centroides da API IBGE Malhas.

from agrobr.normalize import coordenada_para_municipio

# Coordenada para município mais próximo
coordenada_para_municipio(-12.74, -55.68)
# {'codigo_ibge': 5107925, 'nome': 'Sorriso', 'uf': 'MT'}

coordenada_para_municipio(-15.78, -47.93)
# {'codigo_ibge': 5300108, 'nome': 'Brasília', 'uf': 'DF'}

# Oceano / fora do Brasil → None (threshold 1.5° ~167km)
coordenada_para_municipio(0, -30)
# None

Caso de uso típico — filtrar SICAR por município a partir de coordenada:

from agrobr.normalize import coordenada_para_municipio
from agrobr.alt import sicar

info = coordenada_para_municipio(lat, lon)
gdf = await sicar.imoveis_geo(info["uf"], municipio=info["nome"])

Culturas

158 variantes mapeando para 43 culturas canônicas. Aceita português, inglês, com/sem acento.

from agrobr.normalize import normalizar_cultura, listar_culturas, is_cultura_valida

# Padronização
normalizar_cultura("SOJA")             # "soja"
normalizar_cultura("Soja em Grão")     # "soja"
normalizar_cultura("soybean")          # "soja"
normalizar_cultura("milho 2ª safra")   # "milho_2"
normalizar_cultura("café arábica")     # "cafe_arabica"
normalizar_cultura("boi gordo")        # "boi"
normalizar_cultura("cotton")           # "algodao"
normalizar_cultura("cafe_conillon")    # "cafe_robusta"
normalizar_cultura("castanha do pará") # "castanha_do_brasil"

# Listar canônicas
listar_culturas()
# ['acucar', 'acucar_cristal', 'acucar_refinado', 'algodao', 'algodao_pluma',
#  'amendoim', 'arroz', 'aveia', 'batata', 'bezerro', 'boi', 'cafe',
#  'cafe_arabica', 'cafe_robusta', 'cana', 'castanha_do_brasil', 'cebola',
#  'centeio', 'cevada', 'etanol_anidro', 'etanol_hidratado', 'farelo_soja',
#  'feijao', 'feijao_1', 'feijao_2', 'feijao_3', 'frango_congelado',
#  'frango_resfriado', 'laranja', 'laranja_in_natura', 'laranja_industria',
#  'leite', 'mandioca', 'milho', 'milho_1', 'milho_2', 'milho_3', 'oleo_soja',
#  'soja', 'sorgo', 'suino', 'tomate', 'trigo']

# Validação
is_cultura_valida("soja em grão")  # True
is_cultura_valida("batata doce")   # False

UFs e Regiões

27 UFs com código IBGE, nome completo e região. Aceita sigla, nome completo, com/sem acento.

from agrobr.normalize import (
    normalizar_uf, validar_uf, uf_para_nome, uf_para_regiao,
    uf_para_ibge, ibge_para_uf, listar_ufs, listar_regioes,
)

normalizar_uf("São Paulo")     # "SP"
normalizar_uf("sp")            # "SP"
normalizar_uf("SAO PAULO")     # "SP"
normalizar_uf("mato grosso")   # "MT"
normalizar_uf(51)              # InvalidParameterError: entrada deve ser texto, recebeu 51

uf_para_nome("MT")             # "Mato Grosso"
uf_para_regiao("MT")           # "Centro-Oeste"
uf_para_ibge("MT")             # 51
ibge_para_uf(51)               # "MT"
uf_para_nome("XX")             # UnknownNameError, com as siglas válidas

validar_uf("SP")               # True
validar_uf("XX")               # False

listar_ufs()                   # ['AC', 'AL', 'AP', 'AM', ..., 'TO']
listar_ufs("Sul")              # ['PR', 'RS', 'SC']
listar_ufs("sul")              # InvalidParameterError, com as regiões válidas
listar_regioes()               # ['Norte', 'Nordeste', 'Centro-Oeste', 'Sudeste', 'Sul']

UnknownNameError é InvalidParameterError e também KeyError: o except KeyError de antes segue pegando.

normalizar_uf, normalizar_municipio, normalizar_bioma, normalizar_praca (também no produto), slugificar_praca, validar_uf, remover_acentos, municipio_para_ibge e buscar_municipios recusam o que não é texto (número, lista, None) com InvalidParameterError, que diz o parâmetro e o valor recebido. Com texto, o retorno não muda: no normalizar_uf e no municipio_para_ibge, nome que não casa segue devolvendo None; no normalizar_bioma, o próprio texto.

Sem uf, o municipio_para_ibge recusa o nome de mais de um município (521 municípios dividem 240 nomes, como "Bom Jesus" e "Planalto") com o mesmo InvalidParameterError do resolver_municipio, que lista os candidatos; antes, devolvia calado o código do primeiro da lista, muitas vezes de outra UF.

Biomas

6 biomas brasileiros. Aceita com/sem acento, case insensitive. Usada automaticamente em desmatamento, queimadas e mapbiomas.

from agrobr.normalize import normalizar_bioma, BIOMAS_VALIDOS

normalizar_bioma("amazonia")        # "Amazônia"
normalizar_bioma("cerrado")         # "Cerrado"
normalizar_bioma("mata atlantica")  # "Mata Atlântica"
normalizar_bioma("  Caatinga  ")    # "Caatinga"
normalizar_bioma("desconhecido")    # "desconhecido" (passthrough)
normalizar_bioma(None)              # InvalidParameterError: bioma deve ser texto, recebeu None

BIOMAS_VALIDOS
# {'Amazônia', 'Caatinga', 'Cerrado', 'Mata Atlântica', 'Pampa', 'Pantanal'}

Safras

Datas de safra agrícola no formato brasileiro YYYY/YY. A safra agrícola vai de julho a junho.

from agrobr.normalize import (
    safra_atual, normalizar_safra, validar_safra,
    safra_para_anos, anos_para_safra, safra_anterior, safra_posterior,
    periodo_safra, lista_safras,
)

safra_atual()                    # "2025/26" (entre jul/2025 e jun/2026, pela data de Brasília)
normalizar_safra("24/25")        # "2024/25"
normalizar_safra("2024/2025")    # "2024/25"
validar_safra("2024/25")         # True

safra_para_anos("2024/25")       # (2024, 2025)
anos_para_safra(2024, 2025)      # "2024/25"
safra_anterior("2024/25")        # "2023/24"
safra_posterior("2024/25")       # "2025/26"

periodo_safra("2024/25")         # (date(2024, 7, 1), date(2025, 6, 30))
lista_safras("2020/21", "2024/25")
# ['2020/21', '2021/22', '2022/23', '2023/24', '2024/25']
lista_safras(safra_inicio="2023/24", safra_fim="2024/25")  # ['2023/24', '2024/25']

normalizar_safra("abc")          # InvalidParameterError, com os formatos aceitos
anos_para_safra(2024, 2026)      # InvalidParameterError (safra cobre dois anos consecutivos)
validar_safra("2024/23")         # False (anos não consecutivos; também "2024/2024" e "2024/1925")
periodo_safra("2024/23")         # InvalidParameterError (anos não consecutivos)
lista_safras("2025/26", "2024/25")  # InvalidParameterError (intervalo invertido)

Datas das fontes

As datas que chegam de fora como texto passam por dates.converter_coluna, com a mesma regra no pandas 2 e no 3: IBAMA (data_embargo, data_desembargo), Acervo Fundiário (datas do SIGEF, do SNCI e dos assentamentos), CFTC (data), INMET (data das observações), MapBiomas Alerta (data_deteccao, data_publicacao), Queimadas (data_hora_gmt), ANA (data_construcao), ANTAQ (data_atracacao), ANTT Pedágio, FUNAI, IMEA (data_publicacao), INCRA e Embrapa Solos (data_colet).

  • Valor ilegível, ou com ano fora de 1900–2099 (DATA_ANO_MINIMO e DATA_ANO_MAXIMO), vira NaT. No IBAMA, também a data de ato de dia posterior à edição do próprio arquivo (ULTIMA_ATUALIZACAO_RELATORIO).
  • Quando descarta algum valor, a consulta emite UserWarning e põe a mesma mensagem em meta.validation_warnings, com a fonte, a coluna e a quantidade de valores presentes que viraram NaT (célula vazia não conta).
  • O que vem depois é de cada fonte: o INMET descarta a observação sem data (e a mensagem diz isso), o CFTC recusa a resposta com ParseError, e a Embrapa Solos só leva à regra a data de formato válido: data impossível (2024-02-30) ou fora do formato publicado levanta ParseError.

Toda coluna de data das saídas públicas, de fontes e datasets, sai em datetime64[ns], com o fuso quando a fonte publica um, em pandas e em polars (Datetime("ns")). Sem isso, a unidade variava por fonte e por versão do pandas (ns no 2; s, ms ou us no 3), e o join entre datasets pela data falhava no polars. Sem a regra de datas, a data fora do intervalo do datetime64[ns] (antes de 1677 ou depois de 2262) virava NaT no pandas 2 e saía como publicada no pandas 3.

import pandas as pd
from agrobr.normalize import dates

dates.converter_datas(pd.Series(["2024-01-02", "1667-05-31"]), fonte="exemplo")
# DatasConvertidas(datas=[Timestamp('2024-01-02'), NaT] em datetime64[ns], descartadas=1)

Geometrias publicadas

As saídas _geo de polígono entregam a geometria como a fonte publica, sem reparo: desmatamento.prodes_geo e deter_geo, SFB (cnfp_geo, concessoes_geo, ifn_conglomerados_geo), ibama.embargos_geo, funai.terras_indigenas_geo, icmbio.ucs_geo, cnuc.ucs_geo, incra.quilombolas_geo, sicar.imoveis_geo, mapbiomas_alerta.alertas_geo, ANA (hidrografia_geo, pivos_irrigacao_geo, demanda_irrigacao_geo, disponibilidade_hidrica_geo, massas_dagua_geo), Embrapa Solos (mapa_solos_geo, perfis_geo), IBGE (malha_municipal_geo, areas_urbanizadas_geo) e Acervo Fundiário (sigef_geo, snci_geo, assentamentos_geo).

  • Geometria não nula inválida (autointerseção, anel com poucos pontos) gera UserWarning e a mesma frase em meta.validation_warnings, com a contagem em meta.source_details (geometrias_invalidas, geometrias): "IBAMA embargos: 1 de 23 geometrias inválidas como publicadas pela fonte; use make_valid antes de operações espaciais.". Geometria nula não conta. Sem inválidas, nada muda.
  • No recorte oficial do IBAMA dos testes, 1 de 23 polígonos é inválido; num recorte do PRODES no Pará, 292 de 1.728. A conferência custa cerca de 6 µs por polígono.
  • Pontos (queimadas.focos_geo) não passam pela conferência, nem sicar.imoveis_geo_stream, que entrega bloco a bloco (o aviso a cada bloco viraria ruído); confira com gdf.geometry.is_valid se precisar.
  • Para área, interseção ou junção espacial, repare antes: gdf["geometry"] = gdf.geometry.make_valid().

Unidades

Conversão entre unidades agrícolas brasileiras: sacas, toneladas, bushels, arrobas, hectares.

As funções de unidades devolvem Decimal; use float(...) antes de operar com float.

from agrobr.normalize import (
    converter, sacas_para_toneladas, toneladas_para_sacas,
    preco_saca_para_tonelada, preco_tonelada_para_saca,
)

# Conversão genérica
converter(1, "ton", "sc60kg")           # Decimal('16.666…') (sacas de 60kg)
converter(100, "sc60kg", "ton")         # Decimal('6.00')
converter(1, "ton", "bu", produto="soja")  # Decimal('36.7437…') (bushels)
converter(1, "arroba", "kg")            # Decimal('15')

# Atalhos para preços
preco_saca_para_tonelada(145.50)        # Decimal('2425.000…') (R$/ton a partir de R$/sc60kg)
preco_tonelada_para_saca(2425.0)        # Decimal('145.500…') (R$/sc60kg a partir de R$/ton)

# Peso para volume
sacas_para_toneladas(1000)              # Decimal('60')
toneladas_para_sacas(60)                # Decimal('1000')

# Entrada inválida
converter(1, "galao", "kg")             # InvalidParameterError, com as unidades de massa
converter(1, "ton", "bu")               # InvalidParameterError: bushel exige produto (milho, soja, trigo)
sacas_para_toneladas(1000, peso_saca_kg=0)  # InvalidParameterError (peso da saca deve ser positivo)

Encoding

Detecção e decodificação de encoding para HTML/CSV de fontes brasileiras (ISO-8859-1, Windows-1252, UTF-8).

from agrobr.normalize import detect_encoding, decode_content, detect_encoding_chain

# Detectar encoding de bytes (chardet)
encoding, confidence = detect_encoding(raw_bytes)   # ("iso-8859-1", 0.95)

# Decodificar com fallback chain completa
text, enc = decode_content(raw_bytes)               # (str, "utf-8")

# Chain rápida sem chardet (UTF-8-sig → UTF-8 → Windows-1252 → ISO-8859-1)
enc = detect_encoding_chain(raw_bytes)              # "windows-1252"

detect_encoding_chain devolve utf-8-sig quando há BOM e, senão, o primeiro de UTF-8, Windows-1252 e ISO-8859-1 que decodifica o conteúdo inteiro. O ISO-8859-1 decodifica qualquer byte e encerra a chain. decode_content segue a mesma chain depois do encoding declarado. Usadas internamente pelos parsers para CSVs e HTMLs de governo.

Numérico BR

Parsing de valores numéricos no formato brasileiro (ponto como milhar, vírgula como decimal).

from agrobr.normalize import parse_numeric_br

parse_numeric_br("1.234,56")     # 1234.56
parse_numeric_br("1234,56")      # 1234.56
parse_numeric_br("500.000,50")   # 500000.5
parse_numeric_br(42)             # 42.0 (passthrough int/float)
parse_numeric_br("-")            # None (marcador de dado ausente)
parse_numeric_br(None)           # None
parse_numeric_br("abc")          # None (inválido retorna None)

Referência Rápida

Sub-módulo Funções Dados
municipalities municipio_para_ibge, ibge_para_municipio, buscar_municipios, coordenada_para_municipio, total_municipios 5571 municípios + centroides
crops normalizar_cultura, listar_culturas, is_cultura_valida 158 variantes, 43 canônicas
regions normalizar_uf, validar_uf, uf_para_nome, uf_para_regiao, uf_para_ibge, ibge_para_uf, listar_ufs, listar_regioes, normalizar_municipio, normalizar_praca, normalizar_bioma 27 UFs, 6 biomas
dates safra_atual, normalizar_safra, validar_safra, safra_para_anos, anos_para_safra, safra_anterior, safra_posterior, periodo_safra, lista_safras, converter_datas Safras Jul-Jun; datas de 1900 a 2099
units converter, sacas_para_toneladas, toneladas_para_sacas, preco_saca_para_tonelada, preco_tonelada_para_saca sc, ton, bu, @, ha
encoding detect_encoding, decode_content, detect_encoding_chain ISO-8859-1, CP1252, UTF-8
numeric parse_numeric_br Formato BR (1.234,56)