jq: processador de dados json

Guilherme Ferreira

21/01/2023

Introdução

Programas tradicionais como sed, grep ou awk, utilizados para automatização de tarefas em ambientes unix e para processamento de textos, são flexíveis o suficiente para atender às exigências de tratamento de dados em formatos modernos, tal qual json.

No entanto, recomenda-se o uso de jq - ferramenta de linha de comando que facilita o processamento de dados estruturados no formato baseado na sintaxe JavaScript, que é o padrão utilizado para a comunicação de APIs e serviços web, considerando que as funções nativas dispensam o esforço extra de codificação exigido pelo uso de ferramentas clássicas, embora em algumas situações não rejeitem o auxílio de utilitários consagrados e de outras ferramentas de visualização, como csvlook e youplot.

JSON é um acrônimo para JavaScript objetc notation. Sua principal característica é o uso de arrays, estrutura especial de dados capaz de armazenar elementos de diversos tipos, como uma lista de objetos delimitada por colchetes [{}].

Objetivo

Explorar algumas funcionalidades da ferramenta jq, úteis para a manipulação de dados no formato json.

Coleta de dados

Utilizamos os dados abertos da Prefeitura Municipal de Três Corações/MG, relativos às despesas de pessoal, disponíveis no Portal de Transparência do município, que são disponibilizados via API, através do link trescoracoes-mg.portaltp.com.br/api/transparencia.asmx/json_servidores?ano=2017&mes=1.

Cole-o na barra de endereços do navegador ou digite na linha de comando do terminal:
curl -s -k trescoracoes-mg.portaltp.com.br/api/transparencia.asmx/json_servidores?ano=2017&mes=1..

Altere os parâmetros “ano” e “mês”, para obter os dados desejados, e salve o arquivo no diretório de trabalho.

Utilizado no presente artigo, o arquivo json_servidores.xml do mês 11/2022 pode ser baixado em:
https://drive.google.com/file/d/11tC1DnsD0hK3dvosAGVyRdPMI-IOvRJN/view?usp=sharing.

Tarefa de pré-processamento

Embora a estrutura de dados esteja no formato JSON, o arquivo contém a extensão XML.

XML, acrônimo para extensible markup language, pode ser utilizado para criar dados estruturados, com a utilização de tags de abertura (< _ >) e fechamento(</ _ >).

Carregamos o arquivo para visualizar o cabeçalho:

cat json_servidores.xml | less

No cabeçalho do arquivo constam duas declaçaões, delimitadas pelas tags (< >):

Na primeira, estão descritas a versão do protocolo XML (1.0) e o padrão de codificação (Unicode utf-8).
Na segunda, consta um espaço reservado para serviços web em XML, que estejam em desenvolvimento, cujo domínio (URI) pertence à Microsoft.

A seguir, o sinal gráfico para colchete de abertura “[” indica o início de um array, que contém objetos delimitados por chaves “{}”, separados por vírgula. Cada objeto contém o par “key:value”, que é a forma padrão de representação de dados json.

Excluímos o cabeçalho xml e atribuímos ao arquivo a extensão json:

sed -e 's/<[^>]*>//g' json_servidores.xml > servidores.json

Verificamos quais chaves compõem o array:

jq -c  '.[0] | keys_unsorted' servidores.json
## ["ano","mes","unidade_gestora","documento","nome","data_admissao","data_demissao","situacao","matricula","profissao","cargo","regime","centro_custo","horas_semanais","horas_mensais","jornada","local","secretaria","divisao","secao","ato_nomeacao","data_nomecao","numero_concurso","nome_concurso","data_concurso","data_homologacao","numero_lei","nome_padrao","valor_padrao","nome_rem01","valor_rem01","nome_rem02","valor_rem02","nome_rem03","valor_rem03","nome_rem04","valor_rem04","nome_rem05","valor_rem05","nome_rem06","valor_rem06","nome_rem07","valor_rem07","nome_rem08","valor_rem08","nome_rem09","valor_rem09","nome_rem10","valor_rem10","nome_rem11","valor_rem11","nome_rem12","valor_rem12","nome_rem13","valor_rem13","nome_rem14","valor_rem14","nome_rem15","valor_rem15","nome_rem16","valor_rem16","nome_rem17","valor_rem17","nome_rem18","valor_rem18","nome_rem19","valor_rem19","nome_rem20","valor_rem20","nome_rem21","valor_rem21","nome_rem22","valor_rem22","nome_rem23","valor_rem23","nome_rem24","valor_rem24","nome_rem25","valor_rem25","nome_rem26","valor_rem26","nome_rem27","valor_rem27","nome_rem28","valor_rem28","nome_rem29","valor_rem29","nome_rem30","valor_rem30","nome_rem31","valor_rem31","nome_rem32","valor_rem32","nome_rem33","valor_rem33","nome_rem34","valor_rem34","nome_rem35","valor_rem35","nome_rem36","valor_rem36","nome_rem37","valor_rem37","nome_rem38","valor_rem38","nome_rem39","valor_rem39","nome_rem40","valor_rem40"]

Extraímos o par “key:value” do primeiro elemento da lista, para facilitar a seleção das chaves com base nos valores atribuídos. Excluímos o objeto “nome”, para evitar a exposição do servidor, bem como os demais objetos com valores nulos (null), zerados (0) ou vazios(““).

jq '.[0] | del(.nome)' servidores.json | 
jq 'del(.[] | select(. == 0 or . == null or . == ""))'
## {
##   "ano": "2022",
##   "mes": "11",
##   "unidade_gestora": "Prefeitura Municipal de Tres Coracoes",
##   "documento": "***.608.176-**",
##   "data_admissao": "09/03/2021 00:00:00",
##   "data_demissao": "25/10/2022 00:00:00",
##   "situacao": "Demitido",
##   "matricula": "025910",
##   "profissao": "ENFERMEIRO",
##   "cargo": "ENFERMEIRO",
##   "regime": "Serv.Publ.Nao Efetiv",
##   "centro_custo": "BANCO PSF",
##   "horas_semanais": "200:00",
##   "horas_mensais": "200:00",
##   "jornada": "30/12",
##   "local": "Secretaria Municipal de Saude",
##   "secretaria": "Saude da Familia - PSF - CTD. F. 38099",
##   "divisao": "Saude da Familia - PSF - CTD. F. 38099",
##   "secao": "Saude da Familia - PSF - CTD. F. 38099",
##   "nome_padrao": "00-19-09",
##   "valor_padrao": 3152.17,
##   "nome_rem01": "Salario Base",
##   "nome_rem02": "13º Salario",
##   "nome_rem03": "Ferias",
##   "nome_rem04": "Outras Remuneracoes",
##   "valor_rem04": 369.8,
##   "nome_rem05": "Salario Bruto",
##   "valor_rem05": 369.8,
##   "nome_rem06": "Descontos Previdencia",
##   "nome_rem07": "Desconto IRRF",
##   "nome_rem08": "Outros Descontos",
##   "valor_rem10": 369.8
## }

Extração de dados com jq

O utilitário jq possui funções nativas que ajudam na extração de dados:

Há quantos elementos na lista?

jq length servidores.json
## 2733

A estrutura organizacional da prefeitura é composta por quantos cargos distintos?

jq -r 'group_by(.cargo) | 
[.[] | {cargo: .[0].cargo, count: . | length}] | 
length' servidores.json
## 319

Podemos calcular a quantidade de cargos por local.
Por exemplo: a Secretaria de Educação possui quantos cargos?

< servidores.json jq -r '[.[] | 
select(.local | contains("Educacao"))] |
[ group_by(.cargo)[] | 
{cargo: .[0].cargo, count: length}] | 
length'
## 61

Quantas pessoas ocupam cargos na cadeia de comando e assessoramento, abaixo do Executivo?

A princípio, utilizamos somente código jq:


ccomis='[
"SECRETARIO MUNICIPAL", "SECRETARIO ADJUNTO", "DIRETOR", "CHEFE", "ASSESSOR SETORIAL"
]'


jq --argjson ccomis "$ccomis" '[.[] | 
select( .cargo | contains($ccomis[])) |
select( .situacao == "Ativo")] | 
[.[]| .cargo | split(" ")[0:2] | join(" ")] | 
[reduce .[] as $item ({}; .[$item] += 1) | 
to_entries[]] | from_entries' servidores.json
## {
##   "CHEFE DA": 72,
##   "SECRETARIO ADJUNTO": 11,
##   "DIRETOR DO": 57,
##   "DIRETOR (A)": 17,
##   "ASSESSOR SETORIAL": 36,
##   "SECRETARIO MUNICIPAL": 14,
##   "DIRETOR DE": 1
## }

Em seguida, reformulamos a resposta com loop for no bash:

#!/bin/bash -

# Utilizamos shell script para iterar o array de critérios de busca
# Depois de salvo, tornamos o arquivo executável, com o comando:
# sudo chmod +x cargos.sh
# Em seguida, executamos o shell script na linha de comando

cargos=("SECRETARIO MUNICIPAL" "SECRETARIO ADJUNTO" 
  "DIRETOR" "CHEFE" "ASSESSOR SETORIAL")

for i in "${!cargos[@]}"; do
    cargo=${cargos[i]}
  
    qte=$(jq --arg cargo "$cargo" '[.[] | 
  select(.cargo | contains($cargo)) |
  select( .situacao == "Ativo").cargo] |
  length' servidores.json)
  
  echo "${cargos[i]}": $qte
  
done
./cargos.sh
## SECRETARIO MUNICIPAL: 14
## SECRETARIO ADJUNTO: 11
## DIRETOR: 75
## CHEFE: 72
## ASSESSOR SETORIAL: 36

Quantas pessoas exercem o cargo de “Diretor de Departamento”, posto que no levantamento anterior também estão incluídos os “Diretores de Escola”?

< servidores.json jq -r '[ .[] | 
select( .situacao == "Ativo") |
select( .cargo | 
contains("DIRETOR D")).cargo ] | 
length '
## 58

Sabendo que a acumulação de cargos públicos, seguindo algumas regras, é permitida para professores e profissionais de saúde, calculamos quantos servidores se enquadram nessa situação:

< servidores.json jq '[.[].nome] | 
[reduce .[] as $item ({}; .[$item] += 1) | 
to_entries[] | 
select(.value > 1)] | 
from_entries | length'
## 164

Diante disso, podemos determinar com precisão quantas pessoas únicas participam da folha de pagamento do mês 11/2022:

< servidores.json jq '[.[].nome] | 
reduce .[] as $item ({}; .[$item] += 1) | 
length'
## 2569

Qual a situação funcional dos servidores?

jq -r 'group_by(.situacao) | [.[] | {situacao: .[0].situacao, count: . | length}] | 
sort_by(.count) | reverse | .[]| [.situacao, .count] | @csv' servidores.json |
uplot bar -d, -t "Situação funcional"
##                                                                 Situação funcional
##                                                      ┌                                        ┐ 
##                                                Ativo ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2514.0   
##                                Funcionario de Ferias ┤■ 73.0                                    
##                     Licenca Para Tratamento de Saude ┤■ 50.0                                    
##                                             Demitido ┤ 34.0                                     
##                              Licenca sem Remuneracao ┤ 27.0                                     
##                                       Licenca Premio ┤ 11.0                                     
##                                  Licenca Maternidade ┤ 9.0                                      
##                  Pagamento realizado por outro Orgao ┤ 8.0                                      
##                               Suspensao de Pagamento ┤ 2.0                                      
##                 Afastamento Por Acidente de Trabalho ┤ 2.0                                      
##    Licenca por Motivo de Doenca em Pessoa da Familia ┤ 1.0                                      
##                              Licenca com Remuneracao ┤ 1.0                                      
##                                           Aposentado ┤ 1.0                                      
##                                                      └                                        ┘

Qual a natureza do vínculo empregatício?

jq -r 'group_by(.regime) | [.[] | {regime: .[0].regime, count: . | length}] | 
sort_by(.count) | reverse | .[]| [.regime, .count] | @csv' servidores.json |
uplot bar -d, -t "Natureza do vínculo"
##                                        Natureza do vínculo
##                             ┌                                        ┐ 
##        Serv.Publ.Nao Efetiv ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1358.0   
##                Serv.Efetivo ┤■■■■■■■■■■■■■■■■■ 712.0                   
##          Serv Efet Prev Cap ┤■■■■■■■■■■ 430.0                          
##          Serv.Contr.Comiss. ┤■■ 91.0                                   
##         Serv.Efetiv.Comiss. ┤■ 55.0                                    
##        Efet Comiss Prev Cap ┤■ 32.0                                    
##        Ag.Politico Contrat. ┤ 20.0                                     
##          Diretores Efetivos ┤ 15.0                                     
##         Ag.Politico Efetivo ┤ 7.0                                      
##                  Pro-Labore ┤ 6.0                                      
##        Ag Pol Efet Prev Cap ┤ 4.0                                      
##         Diret Efet Prev Cap ┤ 1.0                                      
##           Diret Contratados ┤ 1.0                                      
##    Ag Politico Efetivo RGPS ┤ 1.0                                      
##                             └                                        ┘

Qual a lotação dos servidores por secretaria?

jq -r 'group_by(.local) | [.[] | {local: .[0].local, count: . | length}] | 
sort_by(.count) | reverse | .[]| [.local, .count] | @csv' servidores.json |
uplot bar -d, -t "Lotação dos servidores"
##                                                                Lotação dos servidores
##                                                       ┌                                        ┐ 
##                                Secretaria de Educacao ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1172.0   
##                         Secretaria Municipal de Saude ┤■■■■■■■■■■■■■■■■■■■■ 731.0                
##     Secretaria Municipal de Obras e Servicos Publicos ┤■■■■■■■ 240.0                             
##                  Secretaria de Desenvolvimento Social ┤■■■■ 151.0                                
##        Secretaria de Administracao e Recursos Humanos ┤■■ 82.0                                   
##                                Secretaria de Financas ┤■ 51.0                                    
##                                 Secretaria do Esporte ┤■ 49.0                                    
##                                 Secretaria de Governo ┤■ 46.0                                    
##          Secretaria M de Planejamento e Desenv Urbano ┤■ 41.0                                    
##    Secretaria Municipal do Meio Ambiente e Defesa Ani ┤■ 35.0                                    
##    Secretaria Municipal de Seguranca Publica e Mob Ur ┤■ 30.0                                    
##                Secretaria de Lazer, Turismo e Cultura ┤■ 29.0                                    
##     Secretaria Municipal de Desenvolvimento Economico ┤■ 25.0                                    
##        Secretaria Municipal de Agricultura e Pecuaria ┤■ 23.0                                    
##                                          Procuradoria ┤ 12.0                                     
##              Secretaria Municipal de Controle Interno ┤ 10.0                                     
##    Secretaria de Comunicacao e Relacoes institucionai ┤ 6.0                                      
##                                                       └                                        ┘

Qual a alocação de recursos humanos por centro de custo?

jq -r 'group_by(.centro_custo) | [.[] | {centro_custo: .[0].centro_custo, count: . | 
length}] | sort_by(.count) | reverse | .[]| [.centro_custo, .count] | 
@csv' servidores.json | 
uplot bar -d, -t "Alocação de recursos humanos por centro de custo"
##                                             Alocação de recursos humanos por centro de custo
##                                             ┌                                        ┐ 
##                                      Fundeb ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 889.0   
##                                       Geral ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 794.0       
##                                    SAUDE 15 ┤■■■■■■■■■■■■■■■■■ 449.0                   
##                                25  Educacao ┤■■■■■■■■■■ 272.0                          
##                                   BANCO PSF ┤■■■■■■■■■■ 269.0                          
##                                  Tesouraria ┤■■ 58.0                                   
##                      Secretaria de Educacao ┤ 1.0                                      
##    Secretaria Municipal de Controle Interno ┤ 1.0                                      
##                                             └                                        ┘

Qual o gasto com o funcionalismo em 11/2022?

jq 'map(.valor_rem05) | add | floor' servidores.json
## 9523405

Formatamos a saída para a moeda corrente (com uma nova sintaxe para acesso ao objeto submetido à operação aritmética da adição):

jq '[.. | objects | .valor_rem05] | add' servidores.json | 
awk '{printf "R$ %'\''.2f\n", $0}'
## R$ 9.523.405,09

Qual a despesa de pessoal por centro de custo?

jq -r 'group_by(.centro_custo) | 
map({centro_custo: .[0].centro_custo, total: map(.valor_rem05) | add}) | 
sort_by(.total) | reverse | [.[] | select(.total > 0)] | (first | 
keys_unsorted) as $keys | map([to_entries[] | .value]) as $rows | 
$keys,$rows[] | join(":")' servidores.json | 
awk 'BEGIN{ FS=OFS=":" } NR>1 { $2=sprintf ("R$ %'\''.2f", $2) }1' |
csvlook -d":" 
## | centro_custo | total           |
## | ------------ | --------------- |
## | Geral        | R$ 2.897.591,48 |
## | Fundeb       | R$ 2.727.913,06 |
## | SAUDE 15     | R$ 1.682.549,77 |
## | BANCO PSF    | R$ 1.317.121,78 |
## | 25  Educacao | R$ 610.983,23   |
## | Tesouraria   | R$ 287.245,77   |

Qual a média salarial por centro de custo, sabendo que a mediana é a medida de tendência central mais adequada para distribuições assimétricas?

jq -r 'group_by(.centro_custo) |
map({centro_custo: .[0].centro_custo, media_salarial: map(.valor_rem05) |
(sort|if length%2==1 then.[length/2|floor]else[.[length/2-1,length/2]]|
add/2 end)})|sort_by(.media_salarial)|reverse|[.[]|select(.media_salarial>0)]| 
(first | keys_unsorted) as $keys | map([to_entries[] | .value]) as $rows | 
$keys,$rows[] | join(":")' servidores.json |  
awk 'BEGIN{ FS=OFS=":" } NR>1 { $2=sprintf ("R$ %'\''.2f", $2) }1' | 
csvlook -d":"
## | centro_custo | media_salarial |
## | ------------ | -------------- |
## | Tesouraria   | R$ 3.869,26    |
## | SAUDE 15     | R$ 3.162,43    |
## | Geral        | R$ 3.017,19    |
## | BANCO PSF    | R$ 2.908,80    |
## | Fundeb       | R$ 2.596,43    |
## | 25  Educacao | R$ 1.838,48    |

Podemos também determinar a despesa salarial para uma profissão ou cargo específico.
Nesse caso, utilizamos a string “PROF” como critério de busca, para localizar toda ocorrência que contenha, em qualquer parte, o padrão definido para identificar o cargo de professor:

< servidores.json jq -r '[.[] |
select(.cargo | contains("PROF"))] | 
map({"key": .cargo, "value": .valor_rem05}) | 
sort_by(.value) | reverse | [.[]| select(.value > 0)] | 
map(.value) | add' | 
awk '{printf "R$ %'\''.2f\n", $0}'
## R$ 2.318.158,16

jq possui funções nativas para calcular valores mínimo e máximo:

jq '[.[].valor_rem05] | {"Mínimo":min, "Máximo":max}' servidores.json
## {
##   "Mínimo": 0,
##   "Máximo": 41784.33
## }

O teto remuneratório do funcionalismo público municipal é limitado pelo subsídio do prefeito:

jq '.[] | select(.cargo == "PREFEITO MUNICIPAL").valor_padrao' servidores.json | 
awk '{printf "R$ %'\''.2f\n", $0}'
## R$ 20.801,85

O valor extremo superior representa um supersalário?

jq -r '.[] | select(.valor_rem05 == 41784.33) | 
{"salário base": .valor_padrao}' servidores.json
## {
##   "salário base": 12374.38
## }

Quais verbas salariais contribuiram para gerar esse valor atípico?

< servidores.json jq -r '.[] | select(.valor_rem05 == 41784.33) | 
{"data admissao": .data_admissao, "data demissao": .data_demissao, 
"situacao funcional": .situacao, "cargo": .cargo, "salario base": .valor_padrao, 
"13 salario": .valor_rem02, "Ferias": .valor_rem03, "Outras remuneracoes": .valor_rem04}' 
## {
##   "data admissao": "12/05/2022 00:00:00",
##   "data demissao": "16/11/2022 00:00:00",
##   "situacao funcional": "Demitido",
##   "cargo": "MEDICO - PSF",
##   "salario base": 12374.38,
##   "13 salario": 10214.46,
##   "Ferias": 11286.06,
##   "Outras remuneracoes": 7909.43
## }

No extremo oposto, quantos servidores tiveram a remuneração zerada em 11/2022:

jq '[.[] | select(.valor_rem05 == 0).valor_rem05] | length' servidores.json
## 53

Por quais motivos os salários foram zerados?

< servidores.json jq -r '[.[] | select(.valor_rem05 == 0)] | 
[ group_by(.situacao)[] | {situacao: .[0].situacao, count: length }] | 
sort_by(.count) | reverse | .[] | [.situacao, .count] | @tsv'
## Licenca sem Remuneracao  27
## Licenca Para Tratamento de Saude 15
## Pagamento realizado por outro Orgao  8
## Afastamento Por Acidente de Trabalho 2
## Suspensao de Pagamento   1

Podemos utilizar código nativo em jq para calcular estatísticas básicas:

< servidores.json jq -r '[.[] | select(.valor_rem05>0).valor_rem05] | 
sort| {mean:(add/length), median:(sort|if length%2==1 then.[length/2
|floor]else[.[length/2-1,length/2]]|add/2 end),stdev:((add / length) as $mean | 
(map(. - $mean | . * .) | add) / (length - 1) | sqrt)} | 
.skewness = 3 * (.mean - .median) / .stdev'
## {
##   "mean": 3553.5093619402965,
##   "median": 2908.8,
##   "stdev": 2467.6956446432387,
##   "skewness": 0.783779024783468
## }

Fazemos reuso do código para calcular as estatísticas básicas relativas ao subconjunto do cargo de professor:

< servidores.json jq -r '[.[] | 
select(.cargo | contains("PROF"))] | 
map({"Salario": .valor_rem05}) | [.[]| select(.Salario > 0)] | 
map(.Salario) | sort | 
{min:min, max:max, mean:(add/length), median:(sort|if length%2==1 then.[length/2
|floor]else[.[length/2-1,length/2]]|add/2 end),stdev:((add / length) as $mean | 
(map(. - $mean | . * .) | add) / (length - 1) | sqrt)} | 
.skewness = 3 * (.mean - .median) / .stdev'
## {
##   "min": 412.05,
##   "max": 13904.35,
##   "mean": 3325.9084074605394,
##   "median": 2910.84,
##   "stdev": 1337.1492610146738,
##   "skewness": 0.931238761958941
## }

Podemos recorrer ao auxílio de csvstat para simplificar a tarefa:

jq '.[] | select(.valor_rem05>0).valor_rem05' servidores.json | csvstat -H 
## /usr/local/lib/python3.10/dist-packages/agate/table/from_csv.py:70: RuntimeWarning: Error sniffing CSV dialect: Could not determine delimiter
##   1. "a"
## 
##  Type of data:          Number
##  Contains null values:  False
##  Unique values:         1411
##  Smallest value:        133,73
##  Largest value:         41.784,33
##  Sum:                   9.523.405,09
##  Mean:                  3.553,509
##  Median:                2.908,8
##  StDev:                 2.467,696
##  Most common values:    2.369,49 (200x)
##                         1.437,29 (120x)
##                         2.908,8 (95x)
##                         2.578,99 (53x)
##                         2.269,49 (53x)
## 
## Row count: 2680

Ou executar um script R na linha de comando:

jq '.[] | select(.valor_rem05>0).valor_rem05' servidores.json | 
R -q -e "x <- read.csv('stdin', header = F); summary(x); sd(x[ ,1])" 
## > x <- read.csv('stdin', header = F); summary(x); sd(x[ ,1])
##        V1         
##  Min.   :  133.7  
##  1st Qu.: 2170.5  
##  Median : 2908.8  
##  Mean   : 3553.5  
##  3rd Qu.: 4086.6  
##  Max.   :41784.3  
## [1] 2467.696
## > 
## >

A inspeção visual dos dados pode ser feita com o histograma de frequência:

jq '.[] | select(.valor_rem05>0).valor_rem05' servidores.json | 
sort -gr | uplot hist --nbins 20 
##                       ┌                                        ┐ 
##    [    0.0,  2000.0) ┤▇▇▇▇▇▇▇▇▇▇▇ 477                           
##    [ 2000.0,  4000.0) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 1518   
##    [ 4000.0,  6000.0) ┤▇▇▇▇▇▇▇▇▇ 420                             
##    [ 6000.0,  8000.0) ┤▇▇▇ 153                                   
##    [ 8000.0, 10000.0) ┤▇ 61                                      
##    [10000.0, 12000.0) ┤ 14                                       
##    [12000.0, 14000.0) ┤ 13                                       
##    [14000.0, 16000.0) ┤ 4                                        
##    [16000.0, 18000.0) ┤ 3                                        
##    [18000.0, 20000.0) ┤ 10                                       
##    [20000.0, 22000.0) ┤ 4                                        
##    [22000.0, 24000.0) ┤ 0                                        
##    [24000.0, 26000.0) ┤ 2                                        
##    [26000.0, 28000.0) ┤ 0                                        
##    [28000.0, 30000.0) ┤ 0                                        
##    [30000.0, 32000.0) ┤ 0                                        
##    [32000.0, 34000.0) ┤ 0                                        
##    [34000.0, 36000.0) ┤ 0                                        
##    [36000.0, 38000.0) ┤ 0                                        
##    [38000.0, 40000.0) ┤ 0                                        
##    [40000.0, 42000.0) ┤ 1                                        
##                       └                                        ┘ 
##                                       Frequency

Ou com o diagrama de caixa e bigode (boxplot):

jq '.[] | select(.valor_rem05>0).valor_rem05' servidores.json | 
sort -gr | uplot boxplot -t Variabilidade
##                    Variabilidade
##      ┌                                        ┐ 
##       ╷┬┐                             ╷         
##    1  ├│├─────────────────────────────┤         
##       ╵┴┘                             ╵         
##      └                                        ┘ 
##      0                 25000              50000

Conversão para o formato csv:

Por fim, filtramos os campos desejados para geração do arquivo csv, que permite a análise refinada dos dados com ferramentas mais robustas.


jq -r 'map({matricula, situacao, data_admissao, 
data_demissao, profissao, cargo, regime, local, centro_custo, valor_rem05}) | 
(first | keys_unsorted) as $keys | 
map([to_entries[] | 
.value]) as $rows | 
$keys,$rows[] | join(";")' servidores.json | 
head
## matricula;situacao;data_admissao;data_demissao;profissao;cargo;regime;local;centro_custo;valor_rem05
## 025910;Demitido;09/03/2021 00:00:00;25/10/2022 00:00:00;ENFERMEIRO;ENFERMEIRO;Serv.Publ.Nao Efetiv;Secretaria Municipal de Saude;BANCO PSF;369.8
## 000003;Ativo;01/12/1986 00:00:00;;AGENTE DE SERVICOS PUBLICOS;AGENTE DE SERVICOS PUBLICOS;Serv.Efetivo;Secretaria Municipal de Obras e Servicos Publicos;Geral;4484.84
## 000008;Ativo;20/03/1984 00:00:00;;AGENTE DE SERVICOS PUBLICOS;AGENTE DE SERVICOS PUBLICOS;Serv.Efetivo;Secretaria do Esporte;Geral;4879.73
## 000015;Ativo;24/01/1984 00:00:00;;ENGENHEIRO MECANICO;ENGENHEIRO MECANICO;Serv.Efetivo;Secretaria Municipal de Desenvolvimento Economico;Geral;12452.5
## 000016;Ativo;24/04/1990 00:00:00;;AGENTE DE SERVICOS PUBLICOS;AGENTE DE SERVICOS PUBLICOS;Serv.Efetivo;Secretaria Municipal de Obras e Servicos Publicos;Geral;5757.3
## 000017;Ativo;01/03/1986 00:00:00;;AGENTE DE GESTAO ADMINISTRATIVA;AGENTE DE GESTAO ADMINISTRATIVA;Serv.Efetivo;Secretaria M de Planejamento e Desenv Urbano;Geral;9321.04
## 000018;Ativo;07/04/1987 00:00:00;;AGENTE DE SERVICOS PUBLICOS;CHEFE DA DIVISAO DE ARQUITETURA;Serv.Efetiv.Comiss.;Secretaria M de Planejamento e Desenv Urbano;Geral;4778.34
## 000029;Ativo;26/09/1988 00:00:00;;PROF EDUC BASICA IV;PROF EDUC BASICA IV;Serv.Efetivo;Secretaria de Educacao;Fundeb;6620.14
## 000038;Ativo;09/09/1986 00:00:00;;AGENTE DE SERVICOS PUBLICOS;AGENTE DE SERVICOS PUBLICOS;Serv.Efetivo;Secretaria Municipal de Obras e Servicos Publicos;Geral;4153.26

Conclusão

Evitando fazer inferências, demonstramos algumas funcionalidades de jq que, aliadas a utilitários de linha de comando clássicas e modernas, se transformam em ferramentas eficientes para manipulação de dados json.

Referências:

1) Dolan, Stephen. (2022). jq: lightweight and flexible command-line JSON processor. https://stedolan.github.io/jq/
2) Groskopf, Christopher. (2016). csvkit: suite of command-line tools for working with CSV format. https://csvkit.readthedocs.io/en/latest/index.html
3) Janssens, J. (2015). Data Science at the Command Line.
4) kojix2 (2020). YouPlot : A command line tool for Unicode Plotting. https://dev.to/kojix2/youplot-42d9
5) Robbins, A. & Beebe, H.F. (2008). Classic Shell Scripting.