CoolFace
Datasetpublic

brennercruvinel/news_sources_brazil

news_sources_brazil 17,892 news outlets, one per row, keyed to the IBGE municipality. 16,298 come from atlas da notícia, the survey of local journalism that projor and volt data lab have run since 2017. the other 1,594 are the portals, wire agencies, fact-checkers and international references that truw, my news verification project, was already tracking. the municipality table ships alongside, with 2022 population, HDI, outlet count and the news desert flag, so the whole thing… See the full description on the dataset page: https://huggingface.co/datasets/brennercruvinel/news_sources_brazil.

sourceHugging Faceotherupdated 16d agoView on Hugging Face
0likes176downloads
Dataset Card

newssourcesbrazil

17,892 news outlets, one per row, keyed to the IBGE municipality. 16,298 come from atlas da notícia, the survey of local journalism that projor and volt data lab have run since 2017. the other 1,594 are the portals, wire agencies, fact-checkers and international references that truw, my news verification project, was already tracking. the municipality table ships alongside, with 2022 population, HDI, outlet count and the news desert flag, so the whole thing joins on one key.

this is a registry, not a corpus. there is no article text in here. what you get is who exists, where, in which medium, whether they are still alive, whether they have a feed that answers, and for the larger outlets, what the bias raters say about them. i built it because i needed a single base to answer "who produces news in this town" before pointing a crawler at anything, and it did not exist as parquet with a municipality key.

how it loads

python
from datasets import load_dataset

# the registry, default config
ns = load_dataset("brennercruvinel/news_sources_brazil", "news-sources", split="train")

# the atlas as surveyed
atlas = load_dataset("brennercruvinel/news_sources_brazil", "atlas-veiculos", split="train")

# the municipality lookup
mun = load_dataset("brennercruvinel/news_sources_brazil", "municipios", split="train")

or straight through pandas, which is how i use it:

python
import pandas as pd

base = "hf://datasets/brennercruvinel/news_sources_brazil/data/"
ns = pd.read_parquet(base + "news-sources.parquet")
atlas = pd.read_parquet(base + "atlas-veiculos.parquet")
mun = pd.read_parquet(base + "municipios.parquet")

there is no label and no held-out split. train is the only split because the hub needs one.

the numbers

configrowssizeone row is
news-sources17,8921.6 MBone news source, brazilian or not, with operational and bias metadata
atlas-veiculos16,2980.5 MBone atlas da notícia outlet, as surveyed
municipios5,5710.3 MBone IBGE municipality
outlets in the atlasactiveclosedmunicipalities with an active outletnews deserts
16,29814,8321,4663,0472,503

all three join on cod_ibge. news-sources.atlas_veiculo_id points to atlas-veiculos.atlas_id. every cod_ibge in the first two tables resolves to a row in municipios.

schema

news-sources
columntypewhat it holds
idstringuuid, key
namestringas it appears in the source of origin. atlas names are almost all uppercase
slugstringnormalized name, with state and atlas id appended for atlas rows
domainstringwebsite, 2,282 filled. the atlas does not carry websites, see enrich_candidate_site
categorystringatlas rows: online, radio, impresso, televisao. the rest: digital-native, jornal, tv, revista, agencia, fact-check, tech, portal, agregador, investigativo, and a long tail
countrystringISO 3166-1 alpha-2. 16,514 BR, then US 321, IN 106, UK 75, ES 49
languagestringISO 639-1. 16,538 pt
is_activeboolfalse for the 1,466 outlets the atlas marks as closed
is_verifiedbooltrue for 256 sources flagged as verified in the truw registry
reliability_scoreint6415 to 95. 50 is the default for anything never rated, which is every atlas row. only 1,588 rows carry another value
political_leaningstringcentro, esquerda, direita, centro-esquerda, centro-direita, independente. my own editorial annotation, 1,593 rows, none from the atlas
mbfc_rating, mbfc_factualstringmedia bias/fact check bias and factuality ratings, 63 sources
newsguard_scoreint64newsguard trust score, 58 sources
ifcn_signatoryboolsignatory of the IFCN code of principles, 16 sources
ad_fontes_bias, ad_fontes_reliabilityfloat64ad fontes media chart coordinates, 27 sources
rss_urlstringfeed url, 612 filled
rss_statusstringactive 480, broken 123, none 327, unknown 16,962. unknown means never tested
last_crawled_attimestamplast time the truw crawler fetched the feed, 930 rows
cod_ibgeint64IBGE 7-digit municipality code, atlas rows only
atlas_veiculo_idint64foreign key to atlas-veiculos.atlas_id
fonte_datasetstringwhich base inside the atlas the outlet came from: Atlas da Notícia, MCTIC, Secom/PR, Adjori-*, Central de Diários
enrich_statusstringresult of an automated search for the outlet's website: enriched 1,555, review 2,472, notfound 10,805
enrich_confidencestringsame scale, kept apart for future runs
enrich_candidate_sitestringthe domain the search proposed. a guess, not a confirmation
created_at, updated_attimestamprow lifecycle in the truw database

an atlas row:

json
{
  "id": "000570c6-4ebe-40af-a761-0fc3e8dfe063",
  "name": "CORREIO DO SUL",
  "slug": "correio-do-sul-sc-2657",
  "domain": null,
  "category": "online",
  "country": "BR",
  "language": "pt",
  "is_active": true,
  "is_verified": false,
  "reliability_score": 50,
  "political_leaning": null,
  "rss_status": "unknown",
  "cod_ibge": 4217709,
  "atlas_veiculo_id": 2657,
  "fonte_dataset": "Secom/PR",
  "enrich_status": "review",
  "enrich_candidate_site": "correios.com.br"
}

a rated, non-atlas row:

json
{
  "name": "CIPER Chile",
  "domain": "ciperchile.cl",
  "category": "investigativo",
  "country": "CL",
  "language": "es",
  "reliability_score": 85,
  "political_leaning": "centro-esquerda",
  "cod_ibge": null,
  "atlas_veiculo_id": null
}
atlas-veiculos
columntypewhat it holds
atlas_idint64the atlas's own id, key
nomestringoutlet name as surveyed, 94.8% fully uppercase
fontestringbase of origin inside the atlas. Atlas da Notícia 9,673, MCTIC 4,172, Secom/PR 2,175, Adjori-RS 97, Adjori-SC 91, Central de Diários 48, Adjori-PR 17, Adjori-SP 13, blank 12
segmentostringonline 6,390, radio 5,112, impresso 3,507, televisao 1,287, null 2
cod_ibgeint64IBGE municipality code, always filled
municipio_nome, uf, regiaostringmunicipality name, state, macro region, denormalized from the atlas
num_funcionariosstringstaff size bucket, in portuguese: Um colaborador (operação individual/blog), 1 a 5 colaboradores, 6 a 9, 10 a 29, 30 a 59, 60 a 119, Mais de 120. null for 10,684
periodicidadestringpublishing frequency: diária, semanal, quinzenal, mensal, contínua, and so on. outra for 7,504, null for 363
ativoboolfalse for 1,466
data_atualizacaotimestamplast edit of that record in the atlas, 2019-09-25 to 2026-07-11
data_fechamentodateclosing date, 1,101 rows
created_attimestampwhen the row entered my database, 2026-07-15 for all
json
{
  "atlas_id": 2,
  "nome": "EMPRESA O RIO BRANCO",
  "fonte": "Atlas da Notícia",
  "segmento": "online",
  "cod_ibge": 1200401,
  "municipio_nome": "Rio Branco",
  "uf": "AC",
  "regiao": "Norte",
  "num_funcionarios": null,
  "periodicidade": "outra",
  "ativo": true,
  "data_atualizacao": "2019-10-22T00:33:45+00:00",
  "data_fechamento": null
}
municipios
columntypewhat it holds
cod_ibgeint64IBGE 7-digit code, key
nome, uf, regiaostringname, state, macro region
populacao, ano_popint64resident population and the census year it comes from. 5,570 rows are 2022, one is a 2025 estimate
idhm, idhm_renda, idhm_educacaofloat64municipal human development index, total, income and education components. filled for 2,500 municipalities
is_desertoboolnews desert flag as carried by the atlas, true for 2,503
qtd_veiculosint64count of active atlas outlets headquartered in the municipality. recomputed from atlas-veiculos, matches on every row
lat, lonfloat64centroid of the municipal polygon, WGS84
json
{
  "cod_ibge": 1100015,
  "nome": "Alta Floresta D'Oeste",
  "uf": "RO",
  "regiao": "Norte",
  "populacao": 21494,
  "ano_pop": 2022,
  "idhm": null,
  "is_deserto": false,
  "qtd_veiculos": 3,
  "lat": -12.4702772937175,
  "lon": -62.2745525994542
}

example, towns where radio is the only local outlet

python
active = atlas[atlas.ativo]
per_town = active.pivot_table(index="cod_ibge", columns="segmento", values="atlas_id", aggfunc="count").fillna(0)
radio_only = per_town[(per_town.radio > 0) & (per_town.drop(columns="radio").sum(axis=1) == 0)]
print(len(radio_only), "municipalities where radio is the only local outlet")

df = mun.merge(radio_only, on="cod_ibge")
print(df.groupby("uf").size().sort_values(ascending=False).head())

prints 806, led by MG (140), RS (85), GO (82), SP (56) and BA (56).

how it was built

the atlas da notícia data was imported on july 15, 2026 into the postgres behind truw, one row per surveyed outlet, keeping the atlas's own ids and its fonte column, which records which upstream base each outlet came from. the atlas aggregates its own field survey with the MCTIC broadcasting registry, the Secom/PR media registry, the state Adjori associations of small newspapers and the Central de Diários. i did not merge or deduplicate across those bases, so an outlet registered in two of them appears here twice. the atlas also counts one row per medium, so a brand with print and online editions is two rows on purpose.

every atlas row then became a news-sources row, which is why 16,298 of the 17,892 sources point back to the atlas. the other 1,594 were already in truw's registry: national portals, wire agencies, fact-checkers, and an international reference set. for those, reliability_score and political_leaning are my own annotation, and the MBFC, newsguard and ad fontes fields come from each rater's own site, where the outlet is listed.

since the atlas does not carry websites, i ran every active atlas outlet's name and municipality through a searxng instance and kept the proposed domain as enrich_candidate_site, with a status of enriched when the match was unambiguous, review when it was not, notfound when nothing came back. only 480 sources have a feed confirmed to answer. those are the ones the crawler has been reading since july 2026.

municipios comes from the IBGE municipal registry with the 2022 census population, plus HDI where my registry had it. qtd_veiculos is derived from the atlas, is_deserto is carried over from it.

provenance and licensing

there is no single license to point at, and the card will not pretend otherwise.

the atlas da notícia publishes its data openly through atlas.jor.br/dados, an API, and the Atlas-Analytics repository. none of those states a license: not the site, not the data page, not the repository. base dos dados redistributes the same tables. i checked all four on 2026-09-11 and found no creative commons declaration anywhere, so this card carries license: other instead of guessing a CC version. treat the atlas rows as open data with attribution to projor and volt data lab, which is how everyone else uses them, and cite the atlas if you publish anything derived from atlas-veiculos or the atlas subset of news-sources. if you know the actual terms, open a discussion and i will fix the frontmatter.

the 1,594 truw registry rows, the enrichment columns, the qtd_veiculos derivation and the municipality table are mine and are released under CC BY 4.0. the IBGE codes and population figures are public domain. reliability_score, political_leaning and the rater fields describe organizations, not people, and are labelled as opinion where they are mine.

things that will bite you

reliability_score is 50 for 16,304 rows. that is a default, not a rating. feed it to a model as-is and you are training on a constant. filter on reliability_score != 50.

political_leaning is one person's read on 1,593 outlets, most of them outside brazil. it is not a survey and it covers none of the local outlets. a weak, opinionated label, use it as such.

domain exists for 13% of rows, and enrich_candidate_site fills the gap badly. a small town paper called "Correio do Sul" gets correios.com.br, the postal service, as its candidate. enrich_status == "enriched" is the only tier i would use without a human look.

the same brand shows up more than once. 772 name and municipality pairs repeat, and in every one of them the rows differ in segmento: a newspaper with a print row and an online row, a radio station with a web edition. that is how the atlas counts, one row per medium, not a bug. if you want one row per brand, group on nome and cod_ibge and decide which medium wins. 133 of those pairs also come from different upstream bases, and those are the real double registrations.

periodicidade is dirty. outra for 46% of rows, some english residue (monfri, monsat) that came that way from upstream, and 363 nulls. num_funcionarios is null for two thirds of the outlets.

is_deserto and qtd_veiculos == 0 disagree on 21 municipalities: zero active outlets, but not flagged. the flag comes from the atlas and i did not reproduce its criterion. pick one and say which.

1,378 rows are not brazilian. they are in the file because the truw crawler uses the two sets together. country == "BR" is the filter.

the snapshot is from july 2026. the atlas updates continuously, so an outlet that closed after that still shows as active here. data_atualizacao tells you how stale each row is.

what is not here

article text. truw has crawled about 450,000 articles from these feeds since july 2026, and none of them are in this repository. this is the who, not the what.

municipal polygons. the postgres has them, the parquet does not. they would multiply the size many times over for a table most people will join, not draw, and the centroid covers a dot map.

the raw metadata jsonb of news-sources. everything in it is either flattened into the rater and enrichment columns or lives in atlas-veiculos.

websites for the atlas outlets. the atlas never collected them, and the automated search only recovered 1,555 with confidence.

build

build/export.py regenerates the three parquet files from the truw postgres with the same column selection, flattening and casting used for this release. it needs DATABASE_URL, pandas, pyarrow and psycopg. rerunning it on 2026-09-11 against the live database reproduced atlas-veiculos and municipios byte for byte and news-sources up to the three columns the crawler touches daily, rss_status, last_crawled_at and updated_at.

what changed and why is in CHANGELOG.md.

citation

cite the atlas as the primary source. if this repackaging was useful, cite it as a derivation.

bibtex
@misc{atlasdanoticia,
  title        = {Atlas da Not{\'\i}cia},
  author       = {{Projor} and {Volt Data Lab}},
  year         = {2017},
  howpublished = {\url{https://atlas.jor.br}},
  note         = {snapshot of 2026-07-15}
}

@misc{cruvinel2026newssourcesbrazil,
  title        = {news\_sources\_brazil: a municipality-keyed registry of Brazilian news outlets},
  author       = {Cruvinel, Brenner},
  year         = {2026},
  publisher    = {Hugging Face},
  howpublished = {\url{https://huggingface.co/datasets/brennercruvinel/news_sources_brazil}}
}

author

Brenner Cruvinel, Hoff Research

  • —github: https://github.com/brennercruvinel
  • —contato: brenner@hoffresearch.com