O transfRgov reúne, em um único pacote, dois blocos de dados sobre transferências de recursos da União para municípios brasileiros:
ler_ e
get_;download_transferencias_uniao(),
baixa_municipio_siafibge() e
consultar_renuncias_fiscais().Esta vignette percorre o segundo bloco de ponta a ponta: como baixar
os arquivos mensais de transferências, como acrescentar o código IBGE do
município, como classificar o tipo de destinatário e como consolidar
tudo em uma tabela anual por município e por
função orçamentária. O mesmo fluxo existia apenas como
script solto em
data-raw/consolida_transferencias_p_funcao_municipio.R;
aqui ele é apresentado como documento executável.
O Portal da Transparência publica, para cada mês, um arquivo
https://dadosabertos-download.cgu.gov.br/PortalDaTransparencia/saida/transferencias/AAAA{MM}_Transferencias.zip
com uma linha por combinação de órgão, unidade gestora, município
recebedor, função, subfunção, programa, ação e elemento de despesa. O
download_transferencias_uniao() monta essa URL a partir de
ano e mes, baixa o ZIP para um arquivo
temporário, descompacta, lê o CSV resultante e apaga os temporários.
O CSV usa ; como separador, , como
separador decimal e codificação ISO-8859-1. Os nomes das colunas chegam
em maiúsculas, com espaços e acentos (ANO / MÊS,
NOME FUNÇÃO, VALOR TRANSFERIDO); a função
aplica janitor::clean_names() antes de devolver o
data.frame, então em R os nomes aparecem como
ano_mes, nome_funcao e
valor_transferido.
transferencias_201901 <- download_transferencias_uniao(ano = 2019, mes = 1)
names(transferencias_201901)
#> [1] "ano_mes" "tipo_transferencia"
#> [3] "tipo_favorecido" "uf"
#> [5] "codigo_municipio_siafi" "nome_municipio"
#> ...
#> [35] "nome_favorecido" "valor_transferido"As colunas relevantes para a consolidação são:
| Coluna | Conteúdo |
|---|---|
ano_mes |
competência no formato AAAAMM (por exemplo,
201901) |
tipo_transferencia |
"Constitucionais e Royalties" ou
"Legais, Voluntárias e Específicas" |
codigo_municipio_siafi |
código do município no SIAFI, com zeros à esquerda |
uf, nome_municipio |
unidade da federação e nome do município |
nome_funcao |
função orçamentária (Educação, Saúde,
…) |
nome_modalidade_aplicacao_despesa |
modalidade de aplicação da despesa, que revela o tipo de recebedor |
valor_transferido |
valor transferido no mês |
O código SIAFI não é o identificador usual dos municípios em outras
bases (IBGE, Censo, DATASUS). Passar codigo_ibge = TRUE (o
padrão) faz a função acrescentar a coluna codigo_ibge por
meio de match() contra a tabela de correspondência:
transferencias_201901 <- download_transferencias_uniao(ano = 2019, mes = 1)
# A coluna codigo_ibge é acrescentada casando os códigos SIAFI
transferencias_201901[1, c("codigo_municipio_siafi", "nome_municipio", "codigo_ibge")]
#> codigo_municipio_siafi nome_municipio codigo_ibge
#> 1 4123 Belo Horizonte 3106200Por padrão a função usa o dataset municipios_siafi_ibge,
que acompanha o pacote e fica disponível assim que
library(transfRgov) é executado:
head(municipios_siafi_ibge, 3)
#> # A tibble: 3 × 5
#> codigo_municipio_siafi cnpj nome_municipio uf codigo_ibge
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 0001 05893631000109 GUAJARA-MIRIM RO 1100106
#> 2 0002 84744994000140 ALTO ALEGRE DOS PAREC… RO 1100379
#> 3 0003 05903125000145 PORTO VELHO RO 1100205Como codigo_municipio_siafi é character, os
zeros à esquerda são preservados e o casamento funciona
("0001", e não 1). Se você tem uma tabela de
correspondência mais recente, passe-a em
municipios_mapping =; e se não precisar do código IBGE, use
codigo_ibge = FALSE para economizar o casamento. A função
baixa_municipio_siafibge() baixa a versão publicada hoje
pelo Tesouro Transparente.
O arquivo mensal tem granularidade fina demais para a maior parte das análises. O objetivo aqui é responder: quanto cada município recebeu, por função orçamentária, em um ano? Isso exige quatro passos.
Cada mês é um arquivo separado, então o ano é obtido iterando de 1 a
12 e colando os resultados. data.table::rbindlist() faz o
empilhamento tolerando diferenças de colunas entre os meses. Nesta etapa
também derivamos o ano a partir de ano_mes, dividindo por
100:
baixar_ano <- function(ano) {
data.table::rbindlist(
lapply(1:12, \(mes) {
mes_dados <- download_transferencias_uniao(ano, mes)
if (is.null(mes_dados)) return(NULL)
dplyr::mutate(mes_dados, ano = trunc(ano_mes / 100))
}),
use.names = TRUE
)
}download_transferencias_uniao() nunca lança erro para
falhas de rede ou de leitura: ela emite um warning() e
devolve NULL (invisível). Por isso o
if (is.null(mes_dados)) acima: um mês indisponível
simplesmente não entra na pilha, e rbindlist() descarta os
NULL.
A coluna nome_modalidade_aplicacao_despesa distingue,
entre outras possibilidades, "Transferências a Municípios"
de
"Transferências a Instituições Privadas sem Fins Lucrativos".
Uma única expressão regular separa os dois universos:
dados |>
dplyr::mutate(
privadopub = ifelse(
grepl("Privadas", nome_modalidade_aplicacao_despesa),
"inst_privadas", "publico"
)
)Tudo o que contém "Privadas" vira
inst_privadas; todo o restante (transferências a
municípios, a estados, a consórcios públicos, ao exterior) vira
publico.
Como cada linha do arquivo original é um item de despesa, o próximo passo agrega:
dados |>
dplyr::group_by(ano, codigo_ibge, uf, tipo_transferencia, privadopub, nome_funcao) |>
dplyr::summarise(valor_transferido = sum(valor_transferido, na.rm = TRUE), .groups = "drop") |>
tidyr::pivot_wider(
names_from = c(tipo_transferencia, privadopub),
values_from = valor_transferido,
values_fill = 0
)O pivot_wider() abre tipo_transferencia e
privadopub em colunas, de modo que cada município e função
aparece com os valores separados por tipo de transferência e por tipo de
recebedor, no formato
"<tipo_transferencia>_<privadopub>". Como são
dois tipos de transferência e dois tipos de recebedor, surgem no máximo
quatro colunas.
Vale registrar uma armadilha: os nomes produzidos pelo pivot
são dinâmicos. Nada garante que exista uma coluna chamada
literalmente privadas; o nome real é
"Legais, Voluntárias e Específicas_inst_privadas". Se a sua
base não tiver nenhuma transferência a instituições privadas em um ano,
a coluna simplesmente não é criada. Por isso o próximo passo seleciona
as colunas por padrão (ends_with()), e não por nome
fixo.
O data.frame anterior é longo: uma linha por município,
função e par de colunas. Para comparar municípios entre si, some as
variantes públicas de cada função e abra as funções como colunas:
transfpubswide <- transfpubs_14_24 |>
dplyr::rename_with(janitor::make_clean_names, dplyr::ends_with("_publico")) |>
dplyr::select(ano, codigo_ibge, uf, nome_funcao, dplyr::ends_with("_publico")) |>
dplyr::mutate(
governo_e_publico = rowSums(dplyr::across(dplyr::ends_with("_publico")))
) |>
dplyr::select(ano, codigo_ibge, uf, nome_funcao, governo_e_publico) |>
tidyr::pivot_wider(
names_from = nome_funcao,
values_from = governo_e_publico,
values_fill = 0
) |>
dplyr::mutate(
transftotal = rowSums(dplyr::across(dplyr::where(is.numeric) & -c(ano, codigo_ibge)))
)O rename_with(janitor::make_clean_names, ...) normaliza
nomes como "Legais, Voluntárias e Específicas_publico" para
legais_voluntarias_e_especificas_publico, o que evita
espaços, vírgulas e acentos como nomes de coluna.
O resultado tem uma linha por município e ano, uma coluna por função
orçamentária e a coluna transftotal com a soma das
funções.
Os blocos acima usam eval = FALSE porque dependem de
download e não podem rodar durante a construção do pacote. Para
demonstrar o encadeamento completo sem rede, construímos abaixo um
data.frame pequeno com as mesmas colunas do arquivo
real (já depois de janitor::clean_names()) e os
mesmos valores categóricos: dois tipos de transferência, duas
modalidades de aplicação e as funções Educação,
Saúde e Assistência Social do município de
Belo Horizonte (SIAFI 4123, IBGE 3106200).
transferencias_exemplo <- data.frame(
ano_mes = c(201901L, 201901L, 201901L, 201902L, 201902L, 201902L),
tipo_transferencia = c(
"Constitucionais e Royalties", "Legais, Voluntárias e Específicas",
"Legais, Voluntárias e Específicas", "Constitucionais e Royalties",
"Legais, Voluntárias e Específicas", "Legais, Voluntárias e Específicas"
),
uf = "MG",
codigo_municipio_siafi = "4123",
nome_municipio = "Belo Horizonte",
codigo_ibge = 3106200,
nome_funcao = c(
"Educação", "Saúde", "Assistência Social",
"Educação", "Saúde", "Assistência Social"
),
nome_modalidade_aplicacao_despesa = c(
"Transferências a Municípios",
"Transferências a Municípios - Fundo a Fundo",
"Transferências a Instituições Privadas sem Fins Lucrativos",
"Transferências a Municípios",
"Transferências a Municípios",
"Transferências a Municípios"
),
valor_transferido = c(50000, 80000, 40000, 70000, 100001, 30000)
)
knitr::kable(transferencias_exemplo)| ano_mes | tipo_transferencia | uf | codigo_municipio_siafi | nome_municipio | codigo_ibge | nome_funcao | nome_modalidade_aplicacao_despesa | valor_transferido |
|---|---|---|---|---|---|---|---|---|
| 201901 | Constitucionais e Royalties | MG | 4123 | Belo Horizonte | 3106200 | Educação | Transferências a Municípios | 50000 |
| 201901 | Legais, Voluntárias e Específicas | MG | 4123 | Belo Horizonte | 3106200 | Saúde | Transferências a Municípios - Fundo a Fundo | 80000 |
| 201901 | Legais, Voluntárias e Específicas | MG | 4123 | Belo Horizonte | 3106200 | Assistência Social | Transferências a Instituições Privadas sem Fins Lucrativos | 40000 |
| 201902 | Constitucionais e Royalties | MG | 4123 | Belo Horizonte | 3106200 | Educação | Transferências a Municípios | 70000 |
| 201902 | Legais, Voluntárias e Específicas | MG | 4123 | Belo Horizonte | 3106200 | Saúde | Transferências a Municípios | 100001 |
| 201902 | Legais, Voluntárias e Específicas | MG | 4123 | Belo Horizonte | 3106200 | Assistência Social | Transferências a Municípios | 30000 |
Repare que, em fevereiro, Assistência Social aparece com
a modalidade "Transferências a Municípios" enquanto em
janeiro aparecia como
"Transferências a Instituições Privadas sem Fins Lucrativos":
a mesma função recebe recursos dos dois universos, e é justamente essa
separação que o passo 2 preserva.
O “baixador” abaixo substitui
download_transferencias_uniao() apenas filtrando o exemplo
pelo mês:
baixar_mes <- function(ano, mes) {
transferencias_exemplo[transferencias_exemplo$ano_mes == ano * 100 + mes, , drop = FALSE]
}Com isso, a mesma rotina dos passos 1 a 3 pode ser escrita uma única
vez, recebendo o baixador como argumento. Em uso real, basta omitir
baixar para cair no
download_transferencias_uniao():
consolida_tr_funcao <- function(ano, baixar = download_transferencias_uniao) {
dados <- data.table::rbindlist(
lapply(1:12, \(mes) {
mes_dados <- baixar(ano, mes)
if (is.null(mes_dados)) return(NULL)
dplyr::mutate(mes_dados, ano = trunc(ano_mes / 100))
}),
use.names = TRUE
)
dados |>
dplyr::mutate(
privadopub = ifelse(
grepl("Privadas", nome_modalidade_aplicacao_despesa),
"inst_privadas", "publico"
)
) |>
dplyr::group_by(ano, codigo_ibge, uf, tipo_transferencia, privadopub, nome_funcao) |>
dplyr::summarise(
valor_transferido = sum(valor_transferido, na.rm = TRUE),
.groups = "drop"
) |>
tidyr::pivot_wider(
names_from = c(tipo_transferencia, privadopub),
values_from = valor_transferido,
values_fill = 0
)
}
transfpubs_2019 <- consolida_tr_funcao(2019, baixar = baixar_mes)
knitr::kable(transfpubs_2019)| ano | codigo_ibge | uf | nome_funcao | Constitucionais e Royalties_publico | Legais, Voluntárias e Específicas_inst_privadas | Legais, Voluntárias e Específicas_publico |
|---|---|---|---|---|---|---|
| 2019 | 3106200 | MG | Educação | 120000 | 0 | 0 |
| 2019 | 3106200 | MG | Assistência Social | 0 | 40000 | 30000 |
| 2019 | 3106200 | MG | Saúde | 0 | 0 | 180001 |
O formato longo mostra exatamente o que o pivot produziu: três
colunas de valores (Constitucionais e Royalties_publico,
Legais, Voluntárias e Específicas_publico e
Legais, Voluntárias e Específicas_inst_privadas), uma linha
por função. Duas observações aparecem aqui:
Assistência Social tem 0 em
Constitucionais e Royalties_publico: a função não recebeu
transferências constitucionais, e o values_fill = 0
preencheu a ausência com zero, em vez de deixar NA;Constitucionais e Royalties_inst_privadas, porque
transferências constitucionais não vão para instituições privadas. É o
caso de “coluna que não existe” discutido no passo 3.Por fim, o passo 4 abre as funções em colunas:
transfpubswide <- transfpubs_2019 |>
dplyr::rename_with(janitor::make_clean_names, dplyr::ends_with("_publico")) |>
dplyr::select(ano, codigo_ibge, uf, nome_funcao, dplyr::ends_with("_publico")) |>
dplyr::mutate(
governo_e_publico = rowSums(dplyr::across(dplyr::ends_with("_publico")))
) |>
dplyr::select(ano, codigo_ibge, uf, nome_funcao, governo_e_publico) |>
tidyr::pivot_wider(
names_from = nome_funcao,
values_from = governo_e_publico,
values_fill = 0
) |>
dplyr::mutate(
transftotal = rowSums(dplyr::across(dplyr::where(is.numeric) & -c(ano, codigo_ibge)))
)
knitr::kable(transfpubswide)| ano | codigo_ibge | uf | Educação | Assistência Social | Saúde | transftotal |
|---|---|---|---|---|---|---|
| 2019 | 3106200 | MG | 120000 | 30000 | 180001 | 330001 |
transftotal é a soma das funções: 120000 + 180001 +
30000 = 330001. Note que os 40000 recebidos por instituições privadas em
Assistência Social ficam fora do total, porque o passo 4
soma apenas as variantes _publico. Se o interesse for o
total geral, some também as colunas _inst_privadas.
Consolidar uma série histórica é só repetir a chamada. O
rbindlist() externo cuida de empilhar os anos:
Dois cuidados ao ampliar o período:
pivot_wider() cria as colunas conforme os valores presentes
em cada ano, e values_fill = 0 resolve as ausências, mas o
formato de nome_funcao e de tipo_transferencia
pode variar entre anos. Prefira começar a partir de 2014, primeiro ano
com a estrutura atual dos arquivos mensais;data.frame pré-consolidado no
pacote. O pacote entrega a camada de aquisição; a consolidação
fica com quem analisa, porque as escolhas de agregação (público versus
privado, total versus por função, municipal versus estadual) dependem da
pergunta.valor_transferido são
nominais, tal como publicados. Para séries históricas, deflate
pelo IPCA ou outro índice antes de comparar anos.codigo_ibge recebe NA nesses casos, e não há
erro. Confira sum(is.na(dados$codigo_ibge)) depois do
download.README do projeto.