Introducao

Cinema no Brasil

História, Resiliência e Regulação

A indústria cinematográfica brasileira é caracterizada por uma história de extremos, alternando entre períodos de grande popularidade e crises estruturais. Desde o auge nos anos 1970 — impulsionado por políticas de fomento e o sucesso de filmes populares — até o declínio acentuado nas décadas subsequentes, o mercado sempre demonstrou forte correlação com a estabilidade econômica e o ambiente regulatório do país (AIC - Academia Internacional de Cinema 2019).

Após ser duramente atingido pela pandemia de Covid-19 e pela ascensão do streaming, o circuito exibidor brasileiro mostra sinais de recuperação. Em 2023, o país registrou 114,1 milhões de pagantes e cerca de 3.480 salas, embora ainda aquém dos patamares pré-pandemia (Salgado 2024). Essa recuperação é sustentada por marcos regulatórios recentes, como o restabelecimento da Cota de Tela (Lei nº 14.814/2024) (“Lei Nº 14.814, de 15 de Janeiro de 2024,” n.d.), que obriga exibidoras a dedicarem um número mínimo de sessões à produção nacional até 2033. Essa medida é vista como crucial para proteger o cinema nacional e garantir sua participação efetiva nos horários de maior procura.

O Desafio da Acessibilidade e a Concentração de Salas

Embora a recuperação do público seja um indicador positivo, a infraestrutura de exibição no Brasil apresenta um desafio estrutural: a baixa cobertura e a alta concentração. A migração histórica dos cinemas de rua para o modelo de multiplex em shopping centers resultou em um mercado onde 88,4% das salas estão em shoppings, e apenas 8% dos municípios brasileiros (451 de 5.565) possuem acesso a um cinema (Salgado 2024).

Essa concentração levanta a questão central desta análise: se o acesso à experiência cinematográfica está geograficamente restrito e se essa limitação influencia diretamente os hábitos de consumo e a rentabilidade do setor.

Objetivo e Escopo da Análise

Este relatório apresenta uma análise da forma de consumo e disponibilidade de cinemas e filmes no Brasil em 2024, confrontando a infraestrutura de salas (acessibilidade) com o comportamento de bilheteria (demanda).

O estudo utilizará exclusivamente dados consolidados de bilheteria e cadastro de salas da Agência Nacional do Cinema (ANCINE) referentes ao ano de 2024, visando mapear:

  • Padrões de Consumo: A distribuição temporal (mês, dia, horário) do público e da receita.

  • Alcance Geográfico: A relação entre o número de salas/complexos por UF e município e o desempenho de público e renda.

  • Influência da Infraestrutura: Como a disponibilidade e o tipo de sala (e sua localização) se correlacionam com a capacidade de atração de público no cenário pós-pandemia, sob a vigência de novas políticas como a Cota de Tela.


Origem dos Dados

Nesta seção são apresentadas as fontes de dados utilizadas no estudo, ambas disponibilizadas pela Agência Nacional do Cinema (ANCINE) por meio do Portal Brasileiro de Dados Abertos. As duas bases possuem ampla cobertura nacional e seguem padrões de padronização definidos por instruções normativas específicas.

Relatório de Bilheteria Diária de Obras Informadas pelas Exibidoras

Definição

O conjunto de dados de Relatório de Bilheteria Diária de Obras Informadas pelas Exibidoras reúne informações declaradas pelas exibidoras sobre todas as sessões de cinema realizadas no país. Cada registro corresponde a uma sessão individual e inclui data, horário, obra exibida, características da sala e do exibidor, além de indicadores de público e receita.

Os dados são originalmente organizados por ano e mês, mas cada mês é subdividido em arquivos referentes às semanas consolidadas, no formato:

YYYY-MM-s01  
YYYY-MM-s02  
YYYY-MM-s03  
YYYY-MM-s04  
YYYY-MM-s05

Por exemplo:

  • 2014-01-s01
  • 2014-01-s02
  • 2023-11-s01
  • 2023-11-s02

Essa estrutura permite que a base seja atualizada continuamente, com novas semanas publicadas conforme o envio obrigatório das informações pelas exibidoras. Assim, o conjunto disponibilizado é sempre o mais atualizado possível, porém meses muito recentes podem aparecer ainda incompletos.

Para garantir consistência temporal e evitar problemas decorrentes de semanas não consolidadas, esta análise utiliza exclusivamente os dados referentes a 2024, que apresentam maior estabilidade e cobertura completa.

O envio das informações é regulamentado pela Instrução Normativa nº 123/2015, que define as diretrizes do Sistema de Controle de Bilheteria (SCB).

Mais informações: Instrução Normativa n.º 123, de 22 de dezembro de 2015

Principais campos

A base contém informações como:

  • DATA_EXIBICAO — Data da sessão (dd/MM/yyyy)
  • SESSAO — Data e horário completo da sessão
  • TITULO_ORIGINAL / TITULO_BRASIL — Identificação da obra
  • CPB_ROE — Registro nacional/estrangeiro
  • AUDIO / LEGENDADA — Idioma e acessibilidade
  • REGISTRO_SALA / NOME_SALA — Identificação da sala
  • MUNICIPIO_SALA_COMPLEXO / UF_SALA_COMPLEXO — Localização
  • RAZAO_SOCIAL_EXIBIDORA / CNPJ_EXIBIDORA — Dados da exibidora
  • PUBLICO / RENDA — Indicadores de audiência e receita

Esses campos tornam possível realizar análises temporais, geográficas, econômicas e estruturais do mercado exibidor brasileiro.


Salas de Exibição e Complexos Registrados na ANCINE

Definição

A base de Salas de Exibição e Complexos Registrados na ANCINE reúne informações do cadastro oficial de salas, complexos e exibidores registrados na ANCINE. Inclui dados estruturais (capacidade, acessibilidade, operação), localização, situação de funcionamento e informações cadastrais.

Esse cadastro segue normas estabelecidas pelas instruções normativas:

  • IN nº 91/2010 — sobre cadastro de exibidores
  • IN nº 123/2015 — sobre definição e regulamentação de salas, complexos e funcionamento do SCB

Mais informações:

Principais campos

A base inclui:

  • NOME_SALA / REGISTRO_SALA / CNPJ_SALA — Identificação da sala
  • SITUACAO_SALA — Estado de funcionamento
  • ASSENTOS_SALA — Capacidade total
  • Campos de acessibilidade — Rampas, banheiros acessíveis, etc.
  • NOME_COMPLEXO / REGISTRO_COMPLEXO — Identificação do complexo
  • LOCALIZAÇÃO — Endereço, município, UF
  • OPERACAO_USUAL — Comercial ou não comercial
  • NOME_EXIBIDOR / CNPJ_EXIBIDOR — Dados do exibidor
  • SITUACAO_EXIBIDOR — Regularidade cadastral
  • GRUPO_EXIBIDOR — Quando aplicável

Essa base é fundamental para contextualizar as análises de bilheteria com a infraestrutura de exibição existente no país.


Pacotes Requiridos

Esses são os pacotes necessários para executar a análise descrita nesse documento:

library(readr)
library(dplyr)
## 
## Anexando pacote: 'dplyr'
## Os seguintes objetos são mascarados por 'package:stats':
## 
##     filter, lag
## Os seguintes objetos são mascarados por 'package:base':
## 
##     intersect, setdiff, setequal, union
library(stringr)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats   1.0.1     ✔ purrr     1.1.0
## ✔ ggplot2   4.0.0     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(hms)
## 
## Anexando pacote: 'hms'
## 
## O seguinte objeto é mascarado por 'package:lubridate':
## 
##     hms
library(tidyr)
library(ggplot2)
library(DT)
## Warning: pacote 'DT' foi compilado no R versão 4.5.2
library(scales)
## 
## Anexando pacote: 'scales'
## 
## O seguinte objeto é mascarado por 'package:purrr':
## 
##     discard
## 
## O seguinte objeto é mascarado por 'package:readr':
## 
##     col_factor
library(plotly)
## 
## Anexando pacote: 'plotly'
## 
## O seguinte objeto é mascarado por 'package:ggplot2':
## 
##     last_plot
## 
## O seguinte objeto é mascarado por 'package:stats':
## 
##     filter
## 
## O seguinte objeto é mascarado por 'package:graphics':
## 
##     layout

Carregamento dos Dados

Dados de Bilheterias

Os dados de bilheteria diária e das salas de cinema utilizados neste estudo são disponibilizados pela ANCINE por meio do Portal Brasileiro de Dados Abertos, como explicado da seção de Introdução. Cada mês é dividido em arquivos semanais (por exemplo: 2024-01-s01, 2024-01-s02, …), contendo todas as sessões de cinema realizadas naquela semana.

Embora a base complete constantemente novas semanas ao longo do ano — sendo, portanto, a mais atualizada possível — para esta análise foram utilizados exclusivamente os dados referentes ao ano de 2024, que é o último ano completo no momento dessa análise.

Isso significa que, entre todos os arquivos disponibilizados (2014–2025), foram carregados apenas aqueles que seguem o padrão:

bilheteria-diaria-obras-por-exibidoras-2024-XX-sYY.csv

onde:

  • XX representa o mês (01 a 12)
  • YY representa a semana do mês (s01 a s05, dependendo do calendário)

A opção por limitar a análise ao ano de 2024 se justifica pela necessidade de trabalhar com:

  • um período completo e homogêneo,
  • dados suficientemente recentes,
  • um recorte consistente para comparação temporal,
  • redução de volume, uma vez que a base completa ultrapassa dezenas de milhões de registros.

Além disso, para garantir consistência na correspondência com a base de Salas de Exibição e Complexos, também foi carregada a versão mais recente disponível do cadastro oficial de salas no ano de referência.

Seleção dos arquivos de bilheteria de 2024

Nesta subseção, identificamos na pasta de trabalho apenas os arquivos semanais do ano de 2024. Os nomes seguem o padrão YYYY-MM-sXX, o que permite filtrar os arquivos desejados com expressões regulares. Em seguida, uma função auxiliar extrai o mês de referência de cada arquivo para que possam ser agrupados.

pasta <- "Bilheteria-Salas-Cinema"

# *Assumindo que os arquivos CSV estão na pasta especificada*
arquivos_bilheteria <- list.files(
  pasta,
  pattern = "^bilheteria-diaria-obras-por-exibidoras-2024-\\d{2}-s\\d{2}\\.csv$",
  full.names = TRUE
)

extrair_mes <- function(caminho) {
  nome <- basename(caminho)
  sub(".*(2024-\\d{2}).*", "\\1", nome)
}

meses <- sapply(arquivos_bilheteria, extrair_mes)

Combinação das semanas de cada mês

Cada mês do ano é distribuído em múltiplos arquivos semanais. Nesta etapa, os arquivos são agrupados por mês e combinados em um único data frame mensal. A função map_dfr() é utilizada para ler e empilhar todas as semanas daquele mês, removendo colunas irrelevantes para a análise.

# Usando split() e lapply() para agrupar e carregar
lista_meses_combinados <- lapply(
  split(arquivos_bilheteria, meses),
  function(arquivos_mes) {
    map_dfr(
      arquivos_mes,
      ~read_csv2(
        file = .x,
        locale = locale(encoding = "UTF-8"),
        col_select = -c(REGISTRO_GRUPO_EXIBIDOR, REGISTRO_EXIBIDOR, REGISTRO_COMPLEXO)
      )
    )
  }
)

Motivos para o descarte das colunas:

  • Pouco úteis à análise: códigos internos e informações que não influenciam os indicadores de público e renda.
  • Granularidade excessiva: detalhes de endereço desnecessários para uma análise em nível de município e UF.
  • Baixa qualidade: colunas com muitos valores ausentes, reduzindo confiabilidade e podendo causar perda de dados.

Dados das Salas

Além da base de bilheteria, também é carregado o cadastro oficial de salas e complexos exibidores da ANCINE. Esses metadados são importantes para complementar e validar as informações de exibição presentes nos relatórios de bilheteria. Algumas colunas menos relevantes são descartadas para otimizar o processamento.

df_salas <- read_csv2(
  file = file.path(pasta, "salas-de-exibicao-e-complexos.csv"),
  locale = locale(encoding = "UTF-8"),
  col_select = -c(
    PAGINA_ELETRONICA_COMPLEXO, ENDERECO_COMPLEXO, NUMERO_ENDERECO_COMPLEXO,
    COMPLEMENTO_COMPLEXO, BAIRRO_COMPLEXO, DATA_INICIO_FUNCIONAMENTO_SALA, DATA_SITUACAO_SALA, SITUACAO_SALA, DATA_SITUACAO_COMPLEXO, SITUACAO_COMPLEXO, SITUACAO_EXIBIDOR
  )
)

Motivos para o descarte das colunas:

  • Pouco úteis à análise: códigos internos e informações que não influenciam os indicadores de público e renda.
  • Granularidade excessiva: detalhes de endereço desnecessários para uma análise em nível de município e UF.
  • Baixa qualidade: colunas com muitos valores ausentes, reduzindo confiabilidade e podendo causar perda de dados.

Visualização Inicial

Após o carregamento das bases, realizamos uma inspeção preliminar para verificar a estrutura dos dados, identificar possíveis problemas e garantir que o conteúdo está coerente com o esperado. A seguir, são exibidos exemplos das primeiras linhas de cada uma das fontes utilizadas.

Essa prévia permite inspecionar nomes de colunas, formatos de data, valores ausentes e outras possíveis inconsistências antes de etapas posteriores de limpeza e integração.

Base de Bilheterias

A base de bilheteria foi organizada em uma lista (lista_meses_combinados), na qual cada elemento corresponde a um mês completo de 2024 já resultante da combinação de suas semanas.

A visualização abaixo mostra as primeiras linhas do primeiro mês disponível na lista:

DT::datatable(
      head(lista_meses_combinados[[1]], 10),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )
glimpse(lista_meses_combinados[[1]])
## Rows: 392,667
## Columns: 15
## $ DATA_EXIBICAO           <chr> "01/01/2024", "01/01/2024", "01/01/2024", "01/…
## $ SESSAO                  <chr> "01/01/2024 10:30:00", "01/01/2024 10:40:00", …
## $ TITULO_ORIGINAL         <chr> "WONKA", "FALLEN LEAVES", "MIGRATION", "MIGRAT…
## $ TITULO_BRASIL           <chr> "WONKA", "FOLHAS DE OUTONO", "PATOS!", "PATOS!…
## $ CPB_ROE                 <chr> "E2300218300000", "E2300438800000", "E23004593…
## $ AUDIO                   <chr> "DUBLADO", "ORIGINAL", "DUBLADO", "DUBLADO", "…
## $ LEGENDADA               <chr> "NÃO", "SIM", "NÃO", "NÃO", "NÃO", "NÃO", "NÃO…
## $ PAIS_OBRA               <chr> "ESTADOS UNIDOS", "ALEMANHA", "ESTADOS UNIDOS"…
## $ REGISTRO_SALA           <dbl> 5006728, 5006729, 5006247, 5002819, 5003328, 5…
## $ NOME_SALA               <chr> "SALA 01 DO CENTRO CULTURAL UNIMED BH- MINAS",…
## $ PUBLICO                 <dbl> 2, 3, 0, 0, 4, 6, 19, 8, 0, 0, 2, 3, 4, 9, 1, …
## $ MUNICIPIO_SALA_COMPLEXO <chr> "BELO HORIZONTE", "BELO HORIZONTE", "GOIÂNIA",…
## $ UF_SALA_COMPLEXO        <chr> "MG", "MG", "GO", "SC", "SP", "SC", "SC", "RS"…
## $ RAZAO_SOCIAL_EXIBIDORA  <chr> "MINAS TÊNIS CLUBE", "MINAS TÊNIS CLUBE", "FRE…
## $ CNPJ_EXIBIDORA          <chr> "17.217.951/0001-10", "17.217.951/0001-10", "0…

Base de Salas

A visualização abaixo mostra as primeiras linhas da base de dados que descreve as salas de cinema registradas pela ANCINE:

DT::datatable(
      head(df_salas),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )
glimpse(df_salas)
## Rows: 6,303
## Columns: 21
## $ NOME_SALA                    <chr> "CINE COMPANY JAGUARIUNA", "CINE COMPANY …
## $ REGISTRO_SALA                <dbl> 5003332, 5003331, 5003401, 5003402, 50034…
## $ CNPJ_SALA                    <chr> "10.615.866/0001-60", "10.615.866/0001-60…
## $ ASSENTOS_SALA                <dbl> 162, 162, 402, 370, 245, 332, 210, 137, 1…
## $ ASSENTOS_CADEIRANTES         <dbl> NA, NA, 8, 8, 5, 5, 4, 3, 3, 6, 5, 5, 5, …
## $ ASSENTOS_MOBILIDADE_REDUZIDA <dbl> NA, NA, 5, 4, 3, 3, 3, 1, 1, 4, 3, 3, 3, …
## $ ASSENTOS_OBESIDADE           <dbl> NA, NA, 5, 4, 3, 3, 3, 1, 1, 4, 3, 3, 3, …
## $ ACESSO_ASSENTOS_COM_RAMPA    <chr> "NÃO", "NÃO", "SIM", "SIM", "SIM", "SIM",…
## $ ACESSO_SALA_COM_RAMPA        <chr> "NÃO", "NÃO", "NÃO", "NÃO", "NÃO", "NÃO",…
## $ BANHEIROS_ACESSIVEIS         <chr> "NÃO", "NÃO", "SIM", "SIM", "SIM", "SIM",…
## $ NOME_COMPLEXO                <chr> "02 P D & ARAUJO EMPRESA CINEMATOGRAFICA …
## $ REGISTRO_COMPLEXO            <dbl> 19955, 19955, 20449, 20449, 20449, 20449,…
## $ MUNICIPIO_COMPLEXO           <chr> "JAGUARIÚNA", "JAGUARIÚNA", "SÃO CAETANO …
## $ CEP_COMPLEXO                 <chr> "13820-000", "13820-000", "09531-190", "0…
## $ UF_COMPLEXO                  <chr> "SP", "SP", "SP", "SP", "SP", "SP", "SP",…
## $ COMPLEXO_ITINERANTE          <chr> "NÃO", "NÃO", "NÃO", "NÃO", "NÃO", "NÃO",…
## $ OPERACAO_USUAL               <chr> "COMERCIAL", "COMERCIAL", "COMERCIAL", "C…
## $ NOME_EXIBIDOR                <chr> "02 P D & ARAUJO EMPRESA CINEMATOGRAFICA …
## $ REGISTRO_EXIBIDOR            <dbl> 19954, 19954, 1843, 1843, 1843, 1843, 184…
## $ CNPJ_EXIBIDOR                <chr> "10.615.866/0001-60", "10.615.866/0001-60…
## $ NOME_GRUPO_EXIBIDOR          <chr> "NÃO PERTENCE A NENHUM GRUPO EXIBIDOR", "…

Tratamento dos Dados

Tratamento dos Dados de Bilheteria

Tratamento de NA’s

Aqui vamos ver como está a situação dos dados quanto a presença de NA’s.

# 1. Calcular percentual de NA por coluna em cada mês
na_pct_por_mes <- lista_meses_combinados %>%
  map_dfr(
    ~ colMeans(is.na(.x)) * 100,  # colMeans = média lógica, vira % de NA
    .id = "Mes"
  )

# 2. Transformar para formato longo (tidy)
na_pct_long <- na_pct_por_mes %>%
  pivot_longer(-Mes, names_to = "Coluna", values_to = "Pct_NA")

# 3. Criar o heatmap
ggplot(na_pct_long, aes(x = Mes, y = Coluna, fill = Pct_NA)) +
  geom_tile(color = "white") +
  scale_fill_gradient(
    low = "#FFFFFF",
    high = "#0D47A1",    # sua cor primária
    name = "% NA"
  ) +
  labs(
    title = "Percentual de valores NA por coluna e mês",
    x = "Mês",
    y = "Coluna"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

Observa-se uma maior concentração de valores ausentes nas colunas PAIS_OBRA e TITULO_BRASIL. No caso de PAIS_OBRA, a ausência é esperada, pois esse campo não é preenchido quando o item exibido pertence às categorias “Mostra e Festivais”, “Shows e Musicais” ou “Eventos Esportivos”. Nessas situações, o país de produção não se aplica.

DT::datatable(
      lista_meses_combinados[["2024-01"]] %>% 
        filter(is.na(PAIS_OBRA)) %>% 
        slice(526:530) %>%
        select(DATA_EXIBICAO, TITULO_ORIGINAL, TITULO_BRASIL, PAIS_OBRA),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )

Para evitar que esses casos apareçam como valores faltantes nas análises — o que poderia sugerir erro de preenchimento — optou-se por recodificar esses NA específicos como “Não se aplica”, preservando a interpretação correta do dado.

lista_meses_combinados_tratados <- lista_meses_combinados %>%
   map(~.x %>%
        mutate(
          PAIS_OBRA = ifelse(
            TITULO_ORIGINAL %in% c("Mostras e Festivais", "Shows e Musicais", "Eventos Esportivos"),
            "Não se aplica",
            PAIS_OBRA
          )
        )
    )

DT::datatable(
      lista_meses_combinados_tratados[["2024-01"]] %>% 
        filter(TITULO_ORIGINAL %in% c("Mostras e Festivais", "Shows e Musicais", "Eventos Esportivos")) %>% 
        slice(526:530) %>%
        select(DATA_EXIBICAO, TITULO_ORIGINAL, TITULO_BRASIL, PAIS_OBRA),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )

No caso de TITULO_BRASIL, os valores ausentes ocorrem principalmente porque, para filmes nacionais, apenas a coluna TITULO_ORIGINAL é utilizada, deixando TITULO_BRASIL em branco. Além disso, esse campo também não é preenchido quando o conteúdo exibido pertence às categorias “Mostra e Festivais”, “Shows e Musicais” ou “Eventos Esportivos”.

DT::datatable(
      lista_meses_combinados[["2024-01"]] %>% 
        filter(is.na(TITULO_BRASIL)) %>% 
        slice(1:10) %>%
        select(DATA_EXIBICAO, TITULO_ORIGINAL, TITULO_BRASIL, PAIS_OBRA),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )

Para esse caso, vamos simplesmente repetir o Título Original do filme para a coluna TITULO_BRASIL, já que utilizaremos ela para as análises.

lista_meses_combinados_tratados <- lista_meses_combinados_tratados %>%
  map(~.x %>%
        mutate(
          # Preenche TITULO_BRASIL com TITULO_ORIGINAL se for NA
          TITULO_BRASIL = if_else(is.na(TITULO_BRASIL), TITULO_ORIGINAL, TITULO_BRASIL)
        )
    )

DT::datatable(
      lista_meses_combinados_tratados[["2024-01"]] %>% 
        filter(PAIS_OBRA == "BRASIL") %>% 
        slice(2:6) %>%
        select(DATA_EXIBICAO, TITULO_ORIGINAL, TITULO_BRASIL, PAIS_OBRA),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )

Tratamento de Datas e Horários

Como mencionado anteriormente, a coluna SESSAO armazena tanto a data quanto o horário da sessão em formato de texto. Nesta etapa, extraímos apenas o horário para manter na coluna SESSAO, enquanto a data é preservada na coluna DATA_EXIBICAO. Em seguida, ambas são convertidas para formatos apropriados: DATA_EXIBICAO para o tipo Date e SESSAO para o tipo hms.

lista_meses_combinados_tratados <- lista_meses_combinados_tratados %>%
  map(~.x %>%
        mutate(
          # Trata SESSAO para extrair apenas o horário (HH:mm:ss)
          SESSAO = str_extract(SESSAO, "\\d{2}:\\d{2}:\\d{2}$"),
          DATA_EXIBICAO = as.Date(DATA_EXIBICAO, format = "%d/%m/%Y"),
          SESSAO = as_hms(SESSAO)
        )
  )

Tratamento de Categorias

Nesta etapa, convertemos essas colunas para factor, já que representam variáveis categóricas e devem ser tratadas como tal nas análises subsequentes.

lista_meses_combinados_tratados <- lista_meses_combinados_tratados %>%
  map(~.x %>%
        mutate(
          across(
            c(
              TITULO_ORIGINAL, TITULO_BRASIL, PAIS_OBRA, AUDIO, LEGENDADA,
              MUNICIPIO_SALA_COMPLEXO, UF_SALA_COMPLEXO
            ),
            factor
          )
        )
  )

Visualização Pós-Tratamento

Após os tratamentos realizados:

colSums(is.na(lista_meses_combinados_tratados[["2024-01"]]))
##           DATA_EXIBICAO                  SESSAO         TITULO_ORIGINAL 
##                       0                       0                       0 
##           TITULO_BRASIL                 CPB_ROE                   AUDIO 
##                       0                       0                       0 
##               LEGENDADA               PAIS_OBRA           REGISTRO_SALA 
##                       0                       0                       0 
##               NOME_SALA                 PUBLICO MUNICIPIO_SALA_COMPLEXO 
##                       0                       0                       0 
##        UF_SALA_COMPLEXO  RAZAO_SOCIAL_EXIBIDORA          CNPJ_EXIBIDORA 
##                       0                       0                       0
DT::datatable(
      lista_meses_combinados_tratados[["2024-01"]] %>%
        slice(1:10),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )

Tratamento dos Dados de Salas

Tratamento de NA’s

Aqui apresentamos as colunas que apresentam NA’s e suas quantidades.

df_salas_na <- df_salas %>%
  summarise(across(everything(), ~ sum(is.na(.)))) %>%
  pivot_longer(everything(),
               names_to = "variavel",
               values_to = "n_na") %>%
  filter(n_na > 0)   # << filtra só colunas com NA

ggplot(df_salas_na, aes(x = reorder(variavel, n_na), y = n_na)) +
  geom_col(fill = "#1E88E5") +
  coord_flip() +
  labs(
    title = "Quantidade de valores ausentes por variável — df_salas",
    x = "Variável",
    y = "Nº de valores ausentes"
  ) +
  theme_minimal(base_size = 12)

As colunas ASSENTOS_MOBILIDADE_REDUZIDA, ASSENTOS_OBESIDADE e ASSENTOS_CADEIRANTES apresentam valores ausentes representando ausência de assentos, sendo preenchidas com NA em vez de 0. Portanto, substituímos os NA por 0 nessas variáveis.

df_salas_filtrado <- df_salas %>%
  mutate(
    across(
      c(ASSENTOS_CADEIRANTES, ASSENTOS_MOBILIDADE_REDUZIDA, ASSENTOS_OBESIDADE),
      ~replace_na(.x, 0)
    )
  )

Para os demais casos de valores ausentes, não identificamos uma relação direta — tratam-se simplesmente de informações que não foram fornecidas. Além disso, as linhas que apresentam esses NAs são, em grande parte, as mesmas, o que impede a obtenção de informações relevantes a partir delas.

DT::datatable(
      df_salas_filtrado %>% 
        filter(is.na(REGISTRO_SALA)) %>% 
        slice(6:10),
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )

Por esse motivo, optamos por excluir essas observações do conjunto de dados.

df_salas_filtrado <- df_salas_filtrado %>%
  filter(!is.na(REGISTRO_SALA))

Conversão para fatores

Aqui é selecionado um conjunto de colunas que representam informações categóricas e as converte para o tipo factor, garantindo que essas variáveis sejam tratadas como categorias. Isso padroniza a estrutura do dataframe e facilita análises estatísticas, modelagem e visualizações que dependem de variáveis categóricas bem definidas.

df_salas_filtrado <- df_salas_filtrado %>%
  mutate(
    across(
      c(
        UF_COMPLEXO, ACESSO_ASSENTOS_COM_RAMPA,
        ACESSO_SALA_COM_RAMPA, BANHEIROS_ACESSIVEIS,
        MUNICIPIO_COMPLEXO, COMPLEXO_ITINERANTE, OPERACAO_USUAL
      ),
      factor
    )
  )

Visualização Pós-Tratamento

Após os tratamentos realizados:

colSums(is.na(df_salas_filtrado))
##                    NOME_SALA                REGISTRO_SALA 
##                            0                            0 
##                    CNPJ_SALA                ASSENTOS_SALA 
##                            0                            0 
##         ASSENTOS_CADEIRANTES ASSENTOS_MOBILIDADE_REDUZIDA 
##                            0                            0 
##           ASSENTOS_OBESIDADE    ACESSO_ASSENTOS_COM_RAMPA 
##                            0                            0 
##        ACESSO_SALA_COM_RAMPA         BANHEIROS_ACESSIVEIS 
##                            0                            0 
##                NOME_COMPLEXO            REGISTRO_COMPLEXO 
##                            0                            0 
##           MUNICIPIO_COMPLEXO                 CEP_COMPLEXO 
##                            0                            0 
##                  UF_COMPLEXO          COMPLEXO_ITINERANTE 
##                            0                            0 
##               OPERACAO_USUAL                NOME_EXIBIDOR 
##                            0                            0 
##            REGISTRO_EXIBIDOR                CNPJ_EXIBIDOR 
##                            0                            0 
##          NOME_GRUPO_EXIBIDOR 
##                            0
DT::datatable(
      df_salas_filtrado,
      extensions = "Scroller",
      options = list(
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 300,
        scroller = TRUE
      ),
      rownames = FALSE
    )
## Warning in instance$preRenderHook(instance): It seems your data is too big for
## client-side DataTables. You may consider server-side processing:
## https://rstudio.github.io/DT/server.html

Analise Exploratoria dos Dados

# 1. Público por estado em cada mês
publico_mensal_por_estado <- lista_meses_combinados_tratados %>%
  imap_dfr(~ .x %>% 
             group_by(UF_SALA_COMPLEXO) %>% 
             summarise(publico_estado = sum(PUBLICO, na.rm = TRUE),
                       .groups = "drop") %>% 
             mutate(Mes = .y))

# 2. Público total do mês
publico_total_mes <- publico_mensal_por_estado %>%
  group_by(Mes) %>%
  summarise(publico_total = sum(publico_estado), .groups = "drop")

# 3. Juntar e calcular percentual
percentual_estado_mes <- publico_mensal_por_estado %>%
  left_join(publico_total_mes, by = "Mes") %>%
  mutate(percentual = publico_estado / publico_total)

# 4. Média percentual anual por estado
media_percentual_por_estado <- percentual_estado_mes %>%
  group_by(UF_SALA_COMPLEXO) %>%
  summarise(media_percentual = mean(percentual), .groups = "drop")

# 5. Gráfico

ggplot(media_percentual_por_estado %>%
         arrange(desc(media_percentual)),
       aes(x = reorder(UF_SALA_COMPLEXO, media_percentual), y = media_percentual)) +
  geom_col(fill = "steelblue") +
  scale_y_continuous(labels = scales::label_percent(accuracy = 0.1)) +
  labs(
    title = "Participação Média do Público por Estado",
    x = "Estado",
    y = "Participação Média (%)"
  ) +
  coord_flip() +
  theme_minimal()

# Certificando que Mes é fator para manter a ordem correta
publico_mensal_por_estado <- publico_mensal_por_estado %>%
  mutate(Mes = factor(Mes, levels = unique(Mes)))

# Gráfico ggplot
p <- ggplot(publico_mensal_por_estado,
            aes(x = Mes, 
                y = publico_estado, 
                color = UF_SALA_COMPLEXO, 
                group = UF_SALA_COMPLEXO,
                text = paste("Estado:", UF_SALA_COMPLEXO,
                             "<br>Mês:", Mes,
                             "<br>Público:", format(publico_estado, big.mark = ",")))) +
  geom_line(size = 1.2) +
  geom_point(size = 2) +
  scale_y_continuous(labels = scales::label_number(scale = 1e-6, suffix = "M")) +
  labs(
    title = "Evolução do Público por Estado",
    x = "Mês",
    y = "Público (em milhões)"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Transformando em plotly
ggplotly(p, tooltip = "text") %>%
  layout(
    autosize = TRUE,          # faz o gráfico tentar ocupar todo o espaço disponível
    margin = list(l = 150),   # aumenta margem esquerda para labels longos
    yaxis = list(tickformat = ",")  # formata eixo Y com milhares
  )
# 1. Iterar sobre cada mês e juntar os dados
filmes_mais_assistidos <- lista_meses_combinados_tratados %>%
  imap_dfr(~ .x %>%
             group_by(UF_SALA_COMPLEXO, TITULO_BRASIL, PAIS_OBRA) %>%
             summarise(publico_total = sum(PUBLICO, na.rm = TRUE), .groups = "drop") %>%
             group_by(UF_SALA_COMPLEXO) %>%
             slice_max(order_by = publico_total, n = 1) %>%  # pega o filme mais assistido por estado
             ungroup() %>%
             mutate(Mes = .y))

# Visualizar
filmes_mais_assistidos
## # A tibble: 324 × 5
##    UF_SALA_COMPLEXO TITULO_BRASIL              PAIS_OBRA     publico_total Mes  
##    <fct>            <fct>                      <fct>                 <dbl> <chr>
##  1 AC               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…          7630 2024…
##  2 AL               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         27838 2024…
##  3 AM               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         49397 2024…
##  4 AP               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         10782 2024…
##  5 BA               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         91470 2024…
##  6 CE               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         69536 2024…
##  7 DF               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         60077 2024…
##  8 ES               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         51239 2024…
##  9 GO               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         73011 2024…
## 10 MA               AQUAMAN 2: O REINO PERDIDO ESTADOS UNID…         37824 2024…
## # ℹ 314 more rows
# Pivot para heatmap
origem_filmes_estado <- filmes_mais_assistidos %>%
  select(Mes, UF_SALA_COMPLEXO, PAIS_OBRA)

ggplot(origem_filmes_estado, aes(x = Mes, y = UF_SALA_COMPLEXO, fill = PAIS_OBRA)) +
  geom_tile(color = "white") +
  labs(
    title = "Origem dos Filmes Mais Assistidos por Estado e Mês",
    x = "Mês",
    y = "Estado",
    fill = "País do Filme"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

filme_mais_assistido_pais <- lista_meses_combinados_tratados %>%
  imap_dfr(~ .x %>%
             group_by(TITULO_BRASIL, PAIS_OBRA) %>%
             summarise(publico_total = sum(PUBLICO, na.rm = TRUE), .groups = "drop") %>%
             slice_max(order_by = publico_total, n = 1) %>%  # pega o filme mais assistido do mês
             mutate(Mes = .y))

# Agora você pode criar o gráfico
p <- ggplot(filme_mais_assistido_pais, 
            aes(x = Mes, y = publico_total, fill = PAIS_OBRA,
                text = paste("Filme:", TITULO_BRASIL,
                             "<br>Público:", publico_total,
                             "<br>País:", PAIS_OBRA))) +
  geom_col() +
  labs(
    title = "Filme Mais Assistido no País por Mês",
    x = "Mês",
    y = "Público Total",
    fill = "País do Filme"
  ) +
  theme_minimal()

# Transformando em plotly
ggplotly(p, tooltip = "text") %>%
  layout(
    autosize = TRUE,          # faz o gráfico tentar ocupar todo o espaço disponível
    margin = list(l = 150),   # aumenta margem esquerda para labels longos
    yaxis = list(tickformat = ",")  # formata eixo Y com milhares
  )
# 1. Juntar todos os meses
todos_filmes <- lista_meses_combinados_tratados %>%
  bind_rows() %>%
  select(TITULO_BRASIL, PAIS_OBRA) %>%
  distinct()  # garante que cada filme conte apenas uma vez

# 2. Contar filmes por país (Top 5)
filmes_por_pais_top5 <- todos_filmes %>%
  group_by(PAIS_OBRA) %>%
  summarise(num_filmes = n(), .groups = "drop") %>%
  arrange(desc(num_filmes)) %>%
  slice_head(n = 5)  # pega apenas o top 5

# 3. Gráfico de barras mostrando a quantidade absoluta de filmes por país
p <- ggplot(filmes_por_pais_top5, 
            aes(x = fct_reorder(PAIS_OBRA, num_filmes), y = num_filmes, fill = PAIS_OBRA,
                text = paste("País:", PAIS_OBRA,
                             "<br>Número de Filmes:", num_filmes))) +
  geom_col() +
  geom_text(aes(label = num_filmes), hjust = -0.2, size = 4) +
  coord_flip() +
  labs(
    title = "Top 5 Países por Quantidade de Filmes",
    x = "País",
    y = "Número de Filmes",
    fill = "País"
  ) +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", size = 14))

# Transformar em plotly
ggplotly(p, tooltip = "text") %>%
  layout(
    autosize = TRUE,
    margin = list(l = 150, r = 50, t = 80, b = 50), # aumenta margens para labels e título
    yaxis = list(tickformat = ","),
    xaxis = list(tickangle = 0)
  )
# 1. Filmes únicos por país
filmes_por_pais_tabela <- lista_meses_combinados_tratados %>%
  bind_rows() %>%
  select(TITULO_BRASIL, PAIS_OBRA) %>%
  distinct() %>%
  group_by(PAIS_OBRA) %>%
  summarise(
    num_filmes = n(),
    filmes = paste(TITULO_BRASIL, collapse = ", "),
    .groups = "drop"
  ) %>%
  arrange(desc(num_filmes))

# 2. Mostrar apenas top 5 países
filmes_por_pais_tabela_top5 <- filmes_por_pais_tabela %>%
  slice_head(n = 5)

# 3. Visualizar
filmes_por_pais_tabela_top5
## # A tibble: 5 × 3
##   PAIS_OBRA      num_filmes filmes                                              
##   <fct>               <int> <chr>                                               
## 1 BRASIL                358 "MAMONAS ASSASSINAS O FILME, MINHA IRMÃ E EU, O SEQ…
## 2 ESTADOS UNIDOS        268 "WONKA, PATOS!, AQUAMAN 2: O REINO PERDIDO, JOGOS V…
## 3 FRANÇA                 63 "PARE COM SUAS MENTIRAS, SOFTIE, ALMAS GÊMEAS, NINA…
## 4 ALEMANHA               31 "FOLHAS DE OUTONO, OS TRÊS MOSQUETEIROS: MILADY, AF…
## 5 ESPANHA                20 "SAMSARA, A JORNADA DA ALMA, MEU AMIGO ROBÔ, AS BES…

Conclusão

Referências

AIC - Academia Internacional de Cinema. 2019. A História do Cinema Brasileiro.” Website. https://www.aicinema.com.br/a-historia-do-cinema-brasileiro/.
“Lei Nº 14.814, de 15 de Janeiro de 2024.” n.d. Lei. Presidência da República. http://www.planalto.gov.br/ccivil_03/_ato2023-2026/2024/lei/l14814.htm.
Salgado, Lucas. 2024. O cinema no Brasil vai acabar? Saiba a evolução dos números de público e salas no país.” O Globo, June. https://oglobo.globo.com/cultura/noticia/2024/06/23/cinema-no-brasil-circuito-do-pais-busca-se-recuperar-da-pandemia-e-fazer-frente-ao-streaming.ghtml.