Skip to contents

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.
  • pessoaspeople (victims). One row per person involved, with demographic attributes, injury severity and, for fatalities, the date and place of death.
  • veiculosvehicles. 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")
sinistros

Raw, 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-month Date values.
  • Whitespace. Text columns lose their leading and trailing whitespace. The source pads some fields to a fixed width (nacionalidade ships as "BRASILEIRA "); untrimmed, those values break grouping and joins.
  • Missing values. The "NAO DISPONIVEL" (“not available”) marker becomes NA (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 can sum() or filter them directly. The categorical tp_sinistro_primario stays text.
  • Counts. Missing qtd_* entries remain NA, 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, and numero_logradouro loses 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 by cod_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"—become NA.

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_obito is 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 the veiculos row count for a small share of crashes. Pick one definition and say which. (The qtd_gravidade_* columns do match pessoas exactly.)
  • 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 pessoas row. 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_year

That 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/.