Overview
INFOSIGA-SP is the São Paulo State Traffic Incident Information and Management System, maintained by DETRAN-SP. It publishes open occurrence records from 2015 onward. Coverage and definitions vary over time: 2015–2018 covers fatal crashes only, and the events table also contains notifications that have not yet been confirmed as crashes.
infosigasp downloads and imports those records. It
handles what makes the raw files awkward to read directly.
- The files use Latin-1 (ISO-8859-1), not UTF-8.
-
Semicolons (
;) separate the fields. - Decimal numbers, such as coordinates, use a comma decimal mark.
- Dates follow
DD/MM/YYYY. - Each dataset spans two files (2015–2021 and 2022 onward).
The three datasets
INFOSIGA-SP organises its data into three linked tables.
-
sinistros— occurrence records. One row per confirmed crash or notification, with the date, time, location (including latitude/longitude), road attributes and a breakdown of how many vehicles and victims were involved, by type and severity. -
pessoas— people (victims). One row per person involved, with demographic attributes, injury severity and, for fatalities, the date and place of death. -
veiculos— vehicles. One row per vehicle involved, with make/model, manufacturing and model years, colour and type.
All three share the id_sinistro key, so they can be
joined together; pessoas and veiculos
additionally share id_veiculo.
Reading data
The main entry point is read_infosiga(). The first call
downloads the source archive (about 120 MB) into a per-user cache;
subsequent calls read from that cache, so you only pay the download cost
once. In interactive use, the package asks for confirmation before that
first download.
sinistros <- read_infosiga("sinistros")
sinistrosRaw, typed and clean data
read_infosiga() separates tabular import, type parsing
and cleaning through the processing argument.
| mode | result |
|---|---|
"raw" |
Every field is character. Empty strings, whitespace, sentinels and malformed representations remain visible. |
"typed" |
Documented dates, times and numeric fields receive R classes; other source values remain unchanged. |
"clean" |
The default. Applies the cleaning pipeline after typed import. |
Every mode decodes Latin-1 to UTF-8, parses the CSV structure and combines the period files. Raw mode is therefore a lossless tabular representation rather than a byte-for-byte copy of the source archive.
Choose the mode according to the task: use "raw" to
audit source values, "typed" to parse documented classes
while retaining source labels and padding, and the default
"clean" mode for most analysis. All modes retain import
diagnostics in attr(x, "problems"); typed and clean modes
replace values that fail their declared parser with NA,
while raw mode preserves the field text.
raw <- read_infosiga("sinistros", processing = "raw")
typed <- read_infosiga("sinistros", processing = "typed")
clean <- read_infosiga("sinistros", processing = "clean")Typed mode parses full dates to Date, times to
hms, numeric fields to integer or double, and empty fields
to NA. Identifiers remain character to preserve leading
zeros and avoid integer overflow.
Clean mode then applies the following transformations:
-
Year-month dates. The
ano_mes_*columns (published as"YYYY/MM") become first-of-monthDatevalues. -
Whitespace. Text columns lose their leading and
trailing whitespace. The source pads some fields to a fixed width
(
nacionalidadeships as"BRASILEIRA "); untrimmed, those values break grouping and joins. -
Missing values. The
"NAO DISPONIVEL"(“not available”) marker becomesNA(trimming runs first, so space-padded markers are caught). -
Ordered factors. Ordinal columns sort and plot in
their natural order instead of alphabetically.
-
dia_da_semana:Domingo< … <Sábado(the Brazilian week starts on Sunday); -
turno:MADRUGADA<MANHA<TARDE<NOITE; -
gravidade_lesao(victims):LEVE<GRAVE<FATAL; -
faixa_etaria_demografica/faixa_etaria_legal: age bands in order.
-
-
Crash-type flags. The binary
tp_sinistro_*columns ("S"/ empty) become logical, so you cansum()or filter them directly. The categoricaltp_sinistro_primariostays text. -
Counts. Missing
qtd_*entries remainNA, including when another vehicle or injury-severity count is available for the same crash. Clean mode does not infer that an unreported count is zero. -
Numeric strings.
tempo_sinistro_obito(days from crash to death) becomes integer, andnumero_logradouroloses the spurious trailing".0"the export appends ("193.0"). -
Coordinates. Coordinate pairs outside the São Paulo
state boundary with a 2 km buffer become
NA. The buffer retains plausible near-border records, while the spatial test rejects extreme malformed values,(0, 0)placeholders and points elsewhere inside the state’s rectangular extent. No rows are dropped; typed and raw modes preserve the coordinates before this validation.
Before converting an ordinal, flag or integer column, clean mode validates its observed representations. If it finds an unexpected value, it preserves the entire column and warns; the class can therefore remain character after an upstream change.
These safeguards apply to closed-domain conversions, not to every
parsed column. For example, a malformed numeric coordinate can become
NA during the typed import and will be listed in
attr(x, "problems").
sinistros <- read_infosiga("sinistros")
levels(sinistros$dia_da_semana)dia_da_semana is an ordered factor, so a weekday
tabulation comes out in calendar order rather than alphabetically.
table(sinistros$dia_da_semana)Standardizing category labels
Clean mode does not harmonize nominal categories beyond its
documented missing-value rule. Select only the harmonizations you need
with standardize.
veiculos <- read_infosiga("veiculos", standardize = "cores")
sort(table(veiculos$cor_veiculo), decreasing = TRUE)Vehicle colour is the clearest case. The source carries dozens of distinct values for about sixteen real colours, because two upstream systems coexist in every year, one upper-case and one title-cased, and the two disagree on gender agreement.
| source values | standardized |
|---|---|
PRETA, Preta
|
Preta |
BRANCA, Branco
|
Branca |
VERMELHA, Vermelho
|
Vermelha |
Note that toupper() alone will not merge
Branca and Branco. Detailed liveries and
multi-tone values remain unchanged because grouping them would be an
analytical recode rather than label harmonization.
The remaining changes are listed below.
-
"municipios". Municipality names take their official IBGE spelling, matched bycod_ibge; INFOSIGA administrative-region names receive consistent accents and case without changing their classification. -
"profissoes". Occupations are title-cased, which merges values that differ only by capitalisation. Known nonresponse markers—including variants of"NAO INFORMADA","SEM INFORMACAO"and"DESCONHECIDO"—becomeNA.
Use standardize = c("municipios", "profissoes") to
combine options, or standardize = "all" for every option
applicable to the selected dataset. profissao keeps its
unaccented spelling and occupations stay ungrouped because no
authoritative list covers these free-text values. Standardization
requires processing = "clean".
Joining to other Brazilian data
Always join on cod_ibge, never on the name. IBGE and
INFOSIGA-SP spell nine municipalities differently. Eight contain an
apostrophe that INFOSIGA-SP renders as a space
("Santa Bárbara d'Oeste" against
"SANTA BARBARA D OESTE"), and IBGE’s
"São Luiz do Paraitinga" appears as
"SAO LUIS DO PARAITINGA". A name join loses all nine
silently. cod_ibge is also the standard Brazilian
municipality key, so it joins INFOSIGA-SP to census and population data.
Those are the denominators that turn crash counts into crash
rates.
Inspecting the structure without downloading
The package ships a small sample of each dataset so you can inspect the columns without any network access.
sample_path <- system.file("extdata", "sinistros_sample.csv", package = "infosigasp")
sample <- readr::read_delim(
sample_path,
delim = ";",
show_col_types = FALSE
)
dim(sample)
#> [1] 100 48
names(sample)
#> [1] "id_sinistro" "tipo_registro"
#> [3] "data_sinistro" "ano_sinistro"
#> [5] "mes_sinistro" "dia_sinistro"
#> [7] "hora_sinistro" "ano_mes_sinistro"
#> [9] "dia_da_semana" "turno"
#> [11] "logradouro" "numero_logradouro"
#> [13] "tipo_via" "tipo_local"
#> [15] "latitude" "longitude"
#> [17] "cod_ibge" "municipio"
#> [19] "regiao_administrativa" "administracao"
#> [21] "conservacao" "circunscricao"
#> [23] "tp_sinistro_primario" "qtd_pedestre"
#> [25] "qtd_bicicleta" "qtd_motocicleta"
#> [27] "qtd_automovel" "qtd_onibus"
#> [29] "qtd_caminhao" "qtd_veic_outros"
#> [31] "qtd_veic_nao_disponivel" "qtd_gravidade_fatal"
#> [33] "qtd_gravidade_grave" "qtd_gravidade_leve"
#> [35] "qtd_gravidade_ileso" "qtd_gravidade_nao_disponivel"
#> [37] "tp_sinistro_atropelamento" "tp_sinistro_colisao_frontal"
#> [39] "tp_sinistro_colisao_traseira" "tp_sinistro_colisao_lateral"
#> [41] "tp_sinistro_colisao_transversal" "tp_sinistro_colisao_outros"
#> [43] "tp_sinistro_choque" "tp_sinistro_capotamento"
#> [45] "tp_sinistro_engavetamento" "tp_sinistro_tombamento"
#> [47] "tp_sinistro_outros" "tp_sinistro_nao_disponivel"Coverage and caveats
INFOSIGA-SP publishes one continuous series from 2015 onward, but
what the series contains has changed over time. These are
properties of the source data, not import artefacts, so
infosigasp reports them rather than correcting them.
Several will quietly invalidate an analysis that looks perfectly
reasonable. ?read_infosiga carries the full list; the two
below catch people out most often.
2015–2018 covers fatal crashes only
Non-fatal records begin in 2019. Before that, every row is a fatal crash. Counting crashes per year over the whole series therefore produces a roughly 20-fold jump in 2019 that reflects only the expansion of data collection.
| year | fatal crashes | non-fatal crashes |
|---|---|---|
| 2015 | 5,942 | 0 |
| 2018 | 4,869 | 0 |
| 2019 | 4,804 | 116,412 |
| 2023 | 5,166 | 135,329 |
Those counts come from a 2026 release and shift as DETRAN-SP revises
the data. The break in 2019 does not move. The same break appears in
pessoas, where the LEVE and GRAVE
injury levels simply do not occur before 2019.
So for any trend that reaches back before 2019, restrict to fatalities.
library(dplyr)
read_infosiga("sinistros") |>
filter(tipo_registro == "SINISTRO FATAL") |>
count(ano_sinistro)Otherwise, start the series in 2019 by filtering
ano_sinistro >= 2019 after importing the data.
A third of sinistros rows are notifications
tipo_registro distinguishes confirmed crashes from
"NOTIFICACAO", a reported event not yet confirmed as a
crash. Notifications are about a third of all rows, so treating every
row as a crash overstates the total considerably.
read_infosiga("sinistros") |>
count(tipo_registro, sort = TRUE)DETRAN-SP reclassifies records as it validates them, so the newest months carry an unusually high share of notifications. The last month or two of any release is also incomplete. Drop the tail of the series before reading anything into a recent trend.
Smaller caveats
-
tempo_sinistro_obitois capped at 30 days, the standard convention for attributing a death to a crash. Fatality counts here are 30-day counts. -
The
qtd_*vehicle columns disagree with theveiculosrow count for a small share of crashes. Pick one definition and say which. (Theqtd_gravidade_*columns do matchpessoasexactly.) - Coordinate availability varies by year. It is highest through the middle of the series and lower at both ends, so a mapped subset is not a uniform time sample.
-
About a third of crashes have no
pessoasrow. Use a left join to keep them.
A short analysis
The imported data are ordinary tibbles, so any tidyverse (or base R) workflow applies. Here is a count of traffic fatalities per year from the victims dataset.
library(dplyr)
deaths_by_year <- read_infosiga("pessoas") |>
filter(gravidade_lesao == "FATAL") |>
count(ano_obito, name = "deaths") |>
arrange(ano_obito)
deaths_by_yearThat series is safe to read across all years because it counts only fatalities, the one thing collected consistently since 2015.
Fatalities also break down by the type of victim (driver, passenger, pedestrian).
read_infosiga("pessoas") |>
filter(gravidade_lesao == "FATAL") |>
count(tipo_de_vitima, sort = TRUE)sinistros carries latitude and longitude as numeric
columns, so you can map crash locations directly or aggregate them by
municipality (municipio / cod_ibge).
Updating the data
The package stores the source archive and canonical clean results in the operating-system user cache. A clean result is reused only when its source checksum and cleaning-schema version still match. Standardizations are applied after loading that result and do not create additional artifacts.
DETRAN-SP updates the archive monthly. Use
refresh = TRUE to replace your local copy with the latest
available version and return the requested dataset.
sinistros <- read_infosiga("sinistros", refresh = TRUE)read_infosiga() always imports every available year.
Filter the returned tibble when you need a shorter period.
recent <- read_infosiga("sinistros") |>
filter(ano_sinistro >= 2022)You can point the cache somewhere else for a session (or permanently
via your .Rprofile) with the
infosigasp.cache_dir option.
options(infosigasp.cache_dir = "~/data/infosiga")Inspect cache contents with infosiga_cache_info().
Remove processed results while retaining the source archive with
clear_infosiga_cache(), or pass source = TRUE
to remove both. For a read that should neither use nor create a
processed artifact, set cache = FALSE.
The official data dictionary
dictionary_infosiga() opens the package’s searchable
online data dictionary. Pass "sinistros",
"pessoas" or "veiculos" to open the
corresponding section, or use source = "official" to visit
the INFOSIGA-SP source website.
dictionary_infosiga()
dictionary_infosiga("sinistros")
dictionary_infosiga(source = "official")Citing the data
DETRAN-SP publishes the data under a Creative Commons Attribution 4.0 licence. When you publish results based on these data, please cite INFOSIGA-SP / DETRAN-SP as the source, https://infosiga.detran.sp.gov.br/.
