1 Introdução

1.1 O problema e sua importância

A Netflix reúne filmes e séries de dezenas de países. Mas o que o cadastro do catálogo revela quando é examinado de perto? Filmes ou séries predominam? O acervo ficou mais internacional ao longo dos anos de inclusão? Que gêneros cada país traz para a plataforma, e onde o Brasil aparece nessa composição?

A pergunta central deste trabalho é: o que os registros do catálogo revelam sobre a composição, a internacionalização e a diversidade dos títulos da Netflix até setembro de 2021?

O tema interessa porque o catálogo é a vitrine da plataforma. Sua composição mostra escolhas de licenciamento e produção, o peso de mercados fora dos Estados Unidos e o espaço dado a cada gênero. Para quem estuda mídia ou trabalha com acervos audiovisuais, esses padrões ajudam a entender como uma grande plataforma organiza o que oferece.

1.2 Dados e metodologia

O estudo utiliza o conjunto público Netflix Movies and TV Shows, disponibilizado por Shivam Bansal no Kaggle, com 8.807 registros e 12 colunas: tipo, título, direção, elenco, países, data de inclusão, ano de lançamento, classificação indicativa, duração, categorias e sinopse.

A metodologia é uma análise exploratória e reproduzível em R, organizada em três etapas: diagnóstico da qualidade dos dados, pré-processamento (limpeza, padronização, remoção de duplicatas e criação de variáveis) e exploração com gráficos e tabelas.

1.3 Abordagem proposta

A abordagem central é desagregar o que o arquivo original mistura. Países e categorias aparecem juntos numa mesma célula, separados por vírgulas; datas estão em texto; durações misturam minutos e temporadas; e há títulos repetidos que diferem apenas em letras maiúsculas e minúsculas. Depois de separar e padronizar esses campos, cruzamos as informações com duas tabelas auxiliares: uma de continentes, obtida com o pacote countrycode, e um dicionário de gêneros que traduz e agrupa as 42 categorias originais. Esses cruzamentos permitem perguntas que as colunas brutas não respondem, como a evolução da participação de cada continente ou o perfil de gêneros de cada país.

1.4 Quem pode aproveitar a análise?

Pesquisadores de mídia e plataformas digitais encontram aqui um retrato da composição do catálogo. Profissionais de curadoria e licenciamento podem comparar o perfil de gêneros entre países, inclusive o do Brasil. E quem trabalha com metadados vê, na prática, como pequenos problemas de cadastro (uma duração na coluna errada, um título duplicado) alteram contagens e filtros. Os resultados descrevem o cadastro; não medem audiência nem popularidade.

2 Pacotes requeridos

Todos os pacotes são carregados antes da importação. Se algum estiver faltando, o relatório informa qual instalar.

Pacote Finalidade
readr Importar o CSV e converter texto em número.
dplyr Filtrar, transformar, agregar e juntar tabelas.
tidyr Separar campos com múltiplos valores e alternar entre formato longo e largo.
stringr Padronizar e verificar padrões em textos.
lubridate Converter datas em texto e extrair anos.
countrycode Associar cada país ao seu continente (tabela auxiliar para junção).
ggplot2 Construir os gráficos.
scales Formatar eixos e percentuais.
plotly Tornar os gráficos interativos.
DT Apresentar tabelas com busca, filtros e paginação.
tidytext Separar as sinopses em palavras para análise textual.
knitr Exibir tabelas formatadas no relatório.
pacotes <- c(
  "readr", "dplyr", "tidyr", "stringr", "lubridate", "countrycode",
  "ggplot2", "scales", "plotly", "DT", "tidytext", "knitr"
)
faltantes <- setdiff(pacotes, rownames(installed.packages()))
if (length(faltantes) > 0) {
  stop("Instale os pacotes ausentes: install.packages(c(",
       paste(sprintf('"%s"', faltantes), collapse = ", "), "))")
}
invisible(lapply(pacotes, library, character.only = TRUE))

# Paleta única do relatório (lista nomeada reutilizada em todos os gráficos).
cores_relatorio <- list(
  marinho = "#17365D", azul = "#2E74B5", verde = "#258B69",
  mostarda = "#B07A00", vermelho = "#B94A48", roxo = "#6B5B95",
  texto = "#273548", secundario = "#65758B", grade = "#E4EBF2"
)
cores_tipo <- c("Filme" = cores_relatorio$azul, "Série" = cores_relatorio$verde)

# Tema reutilizável: fundo branco, grade leve e títulos em azul-marinho.
tema_relatorio <- function(tamanho = 12) {
  theme_minimal(base_size = tamanho) +
    theme(
      plot.title = element_text(color = cores_relatorio$marinho, face = "bold",
                                size = rel(1.1)),
      plot.subtitle = element_text(color = cores_relatorio$secundario, size = rel(.88)),
      axis.title = element_text(color = cores_relatorio$texto),
      axis.text = element_text(color = cores_relatorio$secundario),
      legend.title = element_text(color = cores_relatorio$marinho),
      legend.text = element_text(color = cores_relatorio$texto),
      panel.grid.major = element_line(color = cores_relatorio$grade, linewidth = .3),
      panel.grid.minor = element_blank(),
      plot.background = element_rect(fill = "white", color = NA),
      panel.background = element_rect(fill = "white", color = NA)
    )
}
theme_set(tema_relatorio())

# O ggplotly() descarta o subtítulo do ggplot. Esta função converte o gráfico
# e recoloca título e subtítulo no layout do plotly.
interativo <- function(grafico, titulo, subtitulo = NULL, tooltip = "text") {
  texto_titulo <- paste0("<b>", titulo, "</b>")
  if (!is.null(subtitulo)) {
    texto_titulo <- paste0(texto_titulo, "<br><sup>", subtitulo, "</sup>")
  }
  plotly::ggplotly(grafico, tooltip = tooltip) %>%
    plotly::layout(
      title = list(text = texto_titulo, x = 0, xanchor = "left",
                   font = list(color = cores_relatorio$marinho)),
      margin = list(t = 80)
    ) %>%
    plotly::config(displaylogo = FALSE)
}

3 Preparação dos dados

3.1 Fonte e descrição do material original

Fonte: BANSAL, Shivam. Netflix Movies and TV Shows. Kaggle. Disponível em: https://www.kaggle.com/datasets/shivamb/netflix-shows. Acesso em: 28 out. 2026. Licença CC0 (domínio público).

O conjunto foi criado para documentar os metadados dos títulos disponíveis na Netflix. O autor o apresenta como um retrato do catálogo em 2021, coletado a partir de uma ferramenta de busca de terceiros; a data de inclusão mais recente no arquivo é de setembro de 2021. Os valores ausentes estão registrados como campos vazios, que o R interpreta como NA.

O arquivo é tabular, mas tem várias características que exigem tratamento: sinopses em texto livre, células com vários países ou categorias separados por vírgulas, datas em texto no formato americano, durações que misturam duas unidades e inconsistências de cadastro, descritas a seguir.

Coluna original Significado no arquivo
show_id Identificador do registro.
type Filme (Movie) ou série (TV Show).
title Nome do título.
director Direção informada, quando disponível.
cast Elenco informado, quando disponível.
country País ou países de produção, separados por vírgulas.
date_added Data de inclusão na plataforma, em texto (ex.: “September 25, 2021”).
release_year Ano de lançamento original do título.
rating Classificação indicativa americana (não é nota de usuários).
duration Minutos (filmes) ou temporadas (séries), em texto.
listed_in Categorias atribuídas pela Netflix, separadas por vírgulas.
description Sinopse em texto livre, em inglês.
# O CSV original fica em 'dados/' para que a análise possa ser reproduzida.
caminho <- file.path("dados", "netflix_titles.csv")
if (!file.exists(caminho)) stop("CSV não encontrado em: ", caminho)

# Tudo é lido como texto para que nenhuma conversão automática esconda problemas.
dados_originais <- readr::read_csv(
  caminho, na = c("", "NA"), show_col_types = FALSE,
  col_types = readr::cols(.default = readr::col_character(),
                          release_year = readr::col_integer())
)
stopifnot(nrow(dados_originais) > 5000, ncol(dados_originais) > 10)
stopifnot(!anyDuplicated(dados_originais$show_id))

O arquivo importado tem 8.807 linhas e 12 colunas, e cada show_id é único.

3.2 Diagnóstico da qualidade

Antes de alterar qualquer valor, medimos os problemas existentes. Isso define o que precisa ser tratado e permite comparar o antes e o depois.

# 1) Ausências por coluna.
diagnostico <- tibble::tibble(
  coluna = names(dados_originais),
  ausentes = vapply(dados_originais, function(x) sum(is.na(x)), integer(1))
) %>%
  mutate(percentual = 100 * ausentes / nrow(dados_originais)) %>%
  filter(ausentes > 0) %>%
  arrange(desc(ausentes))

knitr::kable(
  diagnostico, digits = 1,
  col.names = c("Coluna", "Valores ausentes", "% dos registros"),
  caption = "Colunas com valores ausentes no arquivo original"
)
Colunas com valores ausentes no arquivo original
Coluna Valores ausentes % dos registros
director 2634 29.9
country 831 9.4
cast 825 9.4
date_added 10 0.1
rating 4 0.0
duration 3 0.0
# 2) Inconsistências de cadastro.
duracao_em_rating <- sum(stringr::str_detect(dados_originais$rating, "min$"), na.rm = TRUE)
virgula_sobrando <- sum(stringr::str_detect(dados_originais$country, ",\\s*$"), na.rm = TRUE)
celulas_com_espacos <- sum(vapply(dados_originais, function(x) {
  if (!is.character(x)) return(0L)
  sum(x != stringr::str_squish(x), na.rm = TRUE)
}, integer(1)))
titulos_caixa <- dados_originais %>%
  mutate(chave = stringr::str_squish(stringr::str_to_lower(title))) %>%
  group_by(chave, type, release_year) %>% filter(n() > 1) %>% ungroup()
n_rotulos_rating <- dplyr::n_distinct(dados_originais$rating, na.rm = TRUE)
n_rotulos_categoria <- dados_originais$listed_in %>%
  stringr::str_split(",") %>% unlist() %>% stringr::str_trim() %>% dplyr::n_distinct()

Além das ausências da tabela, o diagnóstico encontrou:

  • 3 filmes com a duração gravada na coluna de classificação (rating = “74 min” e duration vazia);
  • 5 campos de país terminados em vírgula, que gerariam um país vazio ao separar os valores;
  • 32 células de texto com espaços sobrando no início, no fim ou repetidos;
  • 8 registros que representam 4 títulos repetidos: mesmo nome, tipo e ano de lançamento, mas grafados com maiúsculas e minúsculas diferentes (“Love in a Puff” e “Love In A Puff”) ou com um espaço a mais no fim do nome (dois cadastros de “Consequences”). Uma comparação literal dos textos não encontraria nenhum deles;
  • 17 códigos de classificação indicativa, misturando o padrão de cinema (PG-13, R) e o de TV (TV-14, TV-MA), com dois códigos para “sem classificação” (NR e UR);
  • 42 rótulos de categoria em inglês, com pares equivalentes para filmes e séries (ex.: Dramas e TV Dramas).

A ausência em director, cast e country é informação que a fonte não forneceu. Não a preenchemos por suposição, pois isso criaria dados falsos.

3.3 Pré-processamento

O quadro abaixo resume cada etapa, o motivo e quantos registros foram afetados. Os números são calculados pelo próprio código.

# Etapa 1. Padronizar espaços e converter textos vazios em NA.
dados <- dados_originais %>%
  mutate(across(where(is.character), ~ na_if(stringr::str_squish(.x), "")))

# Etapa 2. Devolver a duração à coluna correta (somente nos casos verificáveis).
erro_duracao <- !is.na(dados$rating) &
  stringr::str_detect(dados$rating, "^[0-9]+\\s+min$") &
  is.na(dados$duration) & dados$type == "Movie"
dados$duration[erro_duracao] <- dados$rating[erro_duracao]
dados$rating[erro_duracao] <- NA_character_

# Etapa 3. Remover duplicatas que só diferem em maiúsculas/minúsculas ou espaços.
# Mantém o registro de inclusão mais recente, que representa o catálogo atual.
dados <- dados %>%
  mutate(titulo_chave = stringr::str_squish(stringr::str_to_lower(title)),
         data_inclusao = lubridate::mdy(date_added, quiet = TRUE)) %>%
  group_by(titulo_chave, type, release_year) %>%
  mutate(registros_no_grupo = n()) %>%
  arrange(desc(data_inclusao), .by_group = TRUE) %>%
  slice(1) %>% ungroup()
duplicatas_removidas <- nrow(dados_originais) - nrow(dados)

# Etapa 4. Unificar "sem classificação" e agrupar códigos em faixas de público.
dados <- dados %>%
  mutate(
    rating = if_else(rating == "UR", "NR", rating),
    faixa_publico = case_when(
      rating %in% c("TV-Y", "TV-Y7", "TV-Y7-FV", "G", "TV-G") ~ "Infantil e livre",
      rating %in% c("PG", "TV-PG") ~ "Orientação dos pais",
      rating %in% c("PG-13", "TV-14") ~ "Adolescente",
      rating %in% c("R", "NC-17", "TV-MA") ~ "Adulto",
      TRUE ~ "Não classificado"
    ),
    faixa_publico = factor(faixa_publico, levels = c(
      "Infantil e livre", "Orientação dos pais", "Adolescente", "Adulto", "Não classificado"))
  )

# Etapa 5. Converter tipos e criar variáveis derivadas.
dados_limpos <- dados %>%
  mutate(
    tipo = factor(if_else(type == "Movie", "Filme", "Série"), levels = c("Filme", "Série")),
    ano_inclusao = lubridate::year(data_inclusao),
    ano_lancamento = release_year,
    duracao_valor = readr::parse_number(duration),
    minutos = if_else(type == "Movie", duracao_valor, NA_real_),
    temporadas = if_else(type == "TV Show", duracao_valor, NA_real_),
    intervalo_anos = ano_inclusao - ano_lancamento,
    n_paises = if_else(is.na(country), NA_integer_,
                       stringr::str_count(stringr::str_remove(country, ",\\s*$"), ",") + 1L),
    n_palavras_sinopse = stringr::str_count(description, "\\b[[:alpha:]]+\\b")
  ) %>%
  select(-titulo_chave, -registros_no_grupo, -duracao_valor)

# Validações: nenhuma duração perdida e datas convertidas sem perda.
stopifnot(!anyDuplicated(dados_limpos$show_id))
stopifnot(all(!is.na(dados_limpos$minutos[dados_limpos$tipo == "Filme"])))
stopifnot(all(!is.na(dados_limpos$temporadas[dados_limpos$tipo == "Série"])))
stopifnot(sum(is.na(dados_limpos$data_inclusao)) == sum(is.na(dados_limpos$date_added)))

quadro_pre <- tibble::tribble(
  ~Etapa, ~Problema, ~Ação, ~Registros,
  "1. Espaços e vazios", "Textos com espaços extras ou vazios",
    "Remoção de espaços e conversão para NA", formatar_n(celulas_com_espacos),
  "2. Coluna trocada", "Duração gravada em `rating`",
    "Duração movida para `duration`; `rating` fica NA", formatar_n(sum(erro_duracao)),
  "3. Duplicatas", "Mesmo título com caixa ou espaço diferente",
    "Mantido o cadastro mais recente", formatar_n(duplicatas_removidas),
  "4. Classificação", "`NR` e `UR` com o mesmo sentido; padrões de cinema e TV",
    "Unificação e criação de `faixa_publico`", formatar_n(nrow(dados_limpos)),
  "5. Tipos e novas variáveis", "Datas e durações em texto",
    "Conversão para data/número e criação de 7 variáveis", formatar_n(nrow(dados_limpos))
)
knitr::kable(quadro_pre, caption = "Resumo do pré-processamento")
Resumo do pré-processamento
Etapa Problema Ação Registros
1. Espaços e vazios Textos com espaços extras ou vazios Remoção de espaços e conversão para NA 32
2. Coluna trocada Duração gravada em rating Duração movida para duration; rating fica NA 3
3. Duplicatas Mesmo título com caixa ou espaço diferente Mantido o cadastro mais recente 4
4. Classificação NR e UR com o mesmo sentido; padrões de cinema e TV Unificação e criação de faixa_publico 8.803
5. Tipos e novas variáveis Datas e durações em texto Conversão para data/número e criação de 7 variáveis 8.803

Após o pré-processamento, a base tem 8.803 títulos únicos. Nenhuma duração ficou ausente. Os 10 registros sem data de inclusão continuam na base, mas ficam fora das comparações por ano. A última inclusão registrada é de 25/09/2021.

As variáveis criadas foram: tipo (fator em português), data_inclusao, ano_inclusao, minutos, temporadas, intervalo_anos (anos entre lançamento e inclusão), n_paises, n_palavras_sinopse e faixa_publico.

3.4 Formato longo, tabelas auxiliares e junções

Contar diretamente as células de país ou categoria esconderia a participação individual de cada valor. Por isso, criamos tabelas em formato longo, com uma linha por par título–país e título–categoria, e as juntamos com duas tabelas auxiliares.

# Tabela longa de países: uma linha por par título-país.
# Cada título distribui peso total 1 entre seus países (contagem fracionada).
paises_longos <- dados_limpos %>%
  select(show_id, tipo, ano_inclusao, country) %>%
  filter(!is.na(country)) %>%
  tidyr::separate_rows(country, sep = ",") %>%
  mutate(pais = stringr::str_trim(country)) %>%
  filter(pais != "") %>%                      # descarta o vazio da vírgula sobrando
  distinct(show_id, tipo, ano_inclusao, pais) %>%
  group_by(show_id) %>% mutate(peso = 1 / n()) %>% ungroup()

# Tabela auxiliar 1: país -> continente e nome em português (pacote countrycode).
continentes_pt <- c(Africa = "África", Americas = "Américas", Asia = "Ásia",
                    Europe = "Europa", Oceania = "Oceania")
tabela_continentes <- tibble::tibble(pais = sort(unique(paises_longos$pais))) %>%
  mutate(continente = countrycode::countrycode(
    pais, origin = "country.name", destination = "continent",
    custom_match = c("East Germany" = "Europe", "Soviet Union" = "Europe")),
    continente = unname(continentes_pt[continente]),
    pais_pt = countrycode::countrycode(
      pais, origin = "country.name", destination = "cldr.short.pt",
      custom_match = c("East Germany" = "Alemanha Oriental",
                       "Soviet Union" = "União Soviética",
                       "West Germany" = "Alemanha Ocidental")))
stopifnot(!anyNA(tabela_continentes$continente), !anyNA(tabela_continentes$pais_pt))

paises_longos <- paises_longos %>% left_join(tabela_continentes, by = "pais")

# Tabela longa de categorias: uma linha por par título-categoria.
categorias_longas <- dados_limpos %>%
  select(show_id, tipo, listed_in) %>%
  tidyr::separate_rows(listed_in, sep = ",") %>%
  mutate(categoria = stringr::str_trim(listed_in)) %>%
  filter(categoria != "") %>%
  distinct(show_id, tipo, categoria)

# Tabela auxiliar 2: dicionário que traduz e agrupa as categorias originais.
# 'natureza' separa gêneros de rótulos de origem/idioma e rótulos genéricos.
dicionario_categorias <- tibble::tribble(
  ~categoria,                       ~genero,                        ~natureza,
  "Dramas",                         "Drama",                        "Gênero",
  "TV Dramas",                      "Drama",                        "Gênero",
  "Comedies",                       "Comédia",                      "Gênero",
  "TV Comedies",                    "Comédia",                      "Gênero",
  "Documentaries",                  "Documentário",                 "Gênero",
  "Docuseries",                     "Documentário",                 "Gênero",
  "Action & Adventure",             "Ação e aventura",              "Gênero",
  "TV Action & Adventure",          "Ação e aventura",              "Gênero",
  "Children & Family Movies",       "Infantil e família",           "Gênero",
  "Kids' TV",                       "Infantil e família",           "Gênero",
  "Romantic Movies",                "Romance",                      "Gênero",
  "Romantic TV Shows",              "Romance",                      "Gênero",
  "Thrillers",                      "Suspense",                     "Gênero",
  "TV Thrillers",                   "Suspense",                     "Gênero",
  "Horror Movies",                  "Terror",                       "Gênero",
  "TV Horror",                      "Terror",                       "Gênero",
  "Sci-Fi & Fantasy",               "Ficção científica e fantasia", "Gênero",
  "TV Sci-Fi & Fantasy",            "Ficção científica e fantasia", "Gênero",
  "Anime Features",                 "Anime",                        "Gênero",
  "Anime Series",                   "Anime",                        "Gênero",
  "Crime TV Shows",                 "Crime",                        "Gênero",
  "TV Mysteries",                   "Mistério",                     "Gênero",
  "Music & Musicals",               "Música e musicais",            "Gênero",
  "Stand-Up Comedy",                "Stand-up e talk show",         "Gênero",
  "Stand-Up Comedy & Talk Shows",   "Stand-up e talk show",         "Gênero",
  "Reality TV",                     "Reality show",                 "Gênero",
  "Sports Movies",                  "Esportes",                     "Gênero",
  "Science & Nature TV",            "Ciência e natureza",           "Gênero",
  "Teen TV Shows",                  "Adolescente",                  "Gênero",
  "LGBTQ Movies",                   "LGBTQ+",                       "Gênero",
  "Faith & Spirituality",           "Fé e espiritualidade",         "Gênero",
  "Classic Movies",                 "Clássicos e cult",             "Gênero",
  "Classic & Cult TV",              "Clássicos e cult",             "Gênero",
  "Cult Movies",                    "Clássicos e cult",             "Gênero",
  "Independent Movies",             "Independente",                 "Gênero",
  "International Movies",           "Internacional",                "Origem/idioma",
  "International TV Shows",         "Internacional",                "Origem/idioma",
  "British TV Shows",               "Britânico",                    "Origem/idioma",
  "Korean TV Shows",                "Coreano",                      "Origem/idioma",
  "Spanish-Language TV Shows",      "Língua espanhola",             "Origem/idioma",
  "Movies",                         "Sem gênero definido",          "Genérico",
  "TV Shows",                       "Sem gênero definido",          "Genérico"
)

categorias_longas <- categorias_longas %>%
  left_join(dicionario_categorias, by = "categoria")
stopifnot(!anyNA(categorias_longas$genero))   # toda categoria foi mapeada

# Formato largo: quantidade de títulos por ano de inclusão e tipo.
tabela_larga <- dados_limpos %>%
  filter(between(ano_inclusao, 2016, 2020)) %>%
  count(ano_inclusao, tipo) %>%
  pivot_wider(names_from = tipo, values_from = n, values_fill = 0)
knitr::kable(tabela_larga, col.names = c("Ano de inclusão", "Filmes", "Séries"),
             caption = "Formato largo: títulos por ano de inclusão e tipo")
Formato largo: títulos por ano de inclusão e tipo
Ano de inclusão Filmes Séries
2016 253 176
2017 839 349
2018 1235 412
2019 1423 591
2020 1284 595

As junções mantêm todos os registros: os 122 países foram associados a um continente e as 42 categorias originais foram agrupadas em 27 grupos. A contagem fracionada evita que um título de dois países conte como dois títulos completos.

3.5 Aparência da base limpa

Uma amostra resume a estrutura final; a tabela interativa permite buscar qualquer título sem imprimir milhares de linhas.

amostra <- dados_limpos %>%
  transmute(ID = show_id, Tipo = tipo, `Título` = title,
            `Lançamento` = ano_lancamento, `Inclusão` = ano_inclusao,
            `Público` = faixa_publico, `Países` = n_paises) %>%
  slice_head(n = 8)
knitr::kable(amostra, caption = "Oito primeiros registros da base preparada")
Oito primeiros registros da base preparada
ID Tipo Título Lançamento Inclusão Público Países
s2037 Filme #Alive 2020 2020 Adulto 1
s2305 Filme #AnneFrank - Parallel Stories 2019 2020 Adolescente 1
s2667 Série #blackAF 2020 2020 Adulto 1
s5973 Filme #cats_the_mewvie 2020 2020 Adolescente 1
s2482 Filme #FriendButMarried 2018 2020 Infantil e livre 1
s2325 Filme #FriendButMarried 2 2020 2020 Infantil e livre 1
s5278 Filme #realityhigh 2017 2017 Adolescente 1
s5974 Filme #Roxy 2018 2019 Adolescente 1
DT::datatable(
  dados_limpos %>%
    transmute(ID = show_id, Tipo = tipo, `Título` = title,
              `Lançamento` = ano_lancamento, `Inclusão` = ano_inclusao,
              `Classificação` = rating, `Público` = faixa_publico,
              `Duração` = duration),
  filter = "top", rownames = FALSE,
  options = list(pageLength = 10, lengthMenu = c(10, 25, 50),
                 scrollX = TRUE, searchHighlight = TRUE),
  caption = "Tabela interativa: busque e filtre os títulos da base limpa"
)

3.6 Resumo das variáveis de interesse

indicadores <- tibble::tibble(
  Variável = c("Registros originais", "Títulos após o pré-processamento",
               "Filmes / séries", "Ano de inclusão (intervalo)",
               "Ano de lançamento (intervalo)", "Duração dos filmes (mediana)",
               "Temporadas das séries (mediana)", "Títulos com país informado",
               "Países distintos / continentes", "Grupos de gênero"),
  Resumo = c(
    formatar_n(nrow(dados_originais)),
    formatar_n(nrow(dados_limpos)),
    paste(formatar_n(sum(dados_limpos$tipo == "Filme")), "/",
          formatar_n(sum(dados_limpos$tipo == "Série"))),
    paste(range(dados_limpos$ano_inclusao, na.rm = TRUE), collapse = " a "),
    paste(range(dados_limpos$ano_lancamento), collapse = " a "),
    paste(median(dados_limpos$minutos, na.rm = TRUE), "minutos"),
    median(dados_limpos$temporadas, na.rm = TRUE),
    formatar_n(sum(!is.na(dados_limpos$country))),
    paste(nrow(tabela_continentes), "/", n_distinct(tabela_continentes$continente)),
    n_distinct(dicionario_categorias$genero[dicionario_categorias$natureza == "Gênero"])
  )
)
knitr::kable(indicadores, caption = "Síntese das principais variáveis após o pré-processamento")
Síntese das principais variáveis após o pré-processamento
Variável Resumo
Registros originais 8.807
Títulos após o pré-processamento 8.803
Filmes / séries 6.128 / 2.675
Ano de inclusão (intervalo) 2008 a 2021
Ano de lançamento (intervalo) 1925 a 2021
Duração dos filmes (mediana) 98 minutos
Temporadas das séries (mediana) 1
Títulos com país informado 7.972
Países distintos / continentes 122 / 5
Grupos de gênero 22

As datas de inclusão se concentram nos últimos anos: 98,4% dos títulos datados entraram de 2016 em diante. Já os anos de lançamento são muito mais antigos e dispersos, o que indica que boa parte do catálogo é formada por obras licenciadas depois de lançadas.

4 Análise exploratória dos dados

A análise vai do retrato geral para cruzamentos mais detalhados. Todos os números referem-se a títulos cadastrados; títulos removidos antes de 2021 não aparecem no arquivo.

4.1 Filme ou série: o que predomina?

composicao <- dados_limpos %>%
  count(tipo) %>% mutate(participacao = n / sum(n))

g_comp <- ggplot(composicao, aes(x = tipo, y = n, fill = tipo,
                                 text = paste0(tipo, ": ", formatar_n(n), " títulos (",
                                               formatar_pct(participacao), ")"))) +
  geom_col(width = .6, show.legend = FALSE) +
  geom_text(aes(label = formatar_n(n)), vjust = -0.5, size = 4) +
  scale_fill_manual(values = cores_tipo) +
  scale_y_continuous(expand = expansion(mult = c(0, .12))) +
  labs(x = NULL, y = "Número de títulos")
interativo(g_comp, "Filmes são cerca de dois terços do catálogo",
           "Quantidade de títulos cadastrados por tipo")

Dos 8.803 títulos, 69,6% são filmes e 30,4% são séries. Esse é o ponto de partida; os cruzamentos a seguir mostram que a proporção muda conforme o ano, o país e o gênero.

4.2 A proporção de séries muda com o ano de inclusão?

A comparação usa 2016 a 2020, anos completos e com volume suficiente; 2021 só vai até setembro.

por_ano <- dados_limpos %>%
  filter(between(ano_inclusao, 2016, 2020)) %>%
  count(ano_inclusao, tipo) %>%
  group_by(ano_inclusao) %>% mutate(participacao = n / sum(n)) %>% ungroup()

g_ano <- ggplot(por_ano, aes(x = ano_inclusao, y = participacao, color = tipo, group = tipo,
                             text = paste0(ano_inclusao, " · ", tipo, ": ",
                                           formatar_pct(participacao), " (", n, " títulos)"))) +
  geom_line(linewidth = 1.1) + geom_point(size = 3) +
  scale_color_manual(values = cores_tipo) +
  scale_x_continuous(breaks = 2016:2020) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1), limits = c(0, 1)) +
  labs(x = "Ano de inclusão", y = "Participação", color = "Tipo")
interativo(g_ano, "A participação das séries caiu até 2018 e voltou a subir",
           "Proporção de filmes e séries entre os títulos incluídos em cada ano")
serie_pct <- function(ano) {
  formatar_pct(por_ano$participacao[por_ano$ano_inclusao == ano & por_ano$tipo == "Série"])
}

As séries eram 41,0% das inclusões de 2016, caíram para 25,0% em 2018 e subiram para 31,7% em 2020. Em 2018 a plataforma incorporou um grande volume de filmes, o que diluiu a participação das séries. A trajetória não é linear.

4.3 O catálogo ficou mais internacional?

Esta é a pergunta que as colunas originais não respondem sozinhas. Ela exige separar os países, aplicar a contagem fracionada e juntar a tabela de continentes.

internacional_ano <- paises_longos %>%
  filter(between(ano_inclusao, 2015, 2020)) %>%
  group_by(ano_inclusao) %>%
  summarise(fora_eua = sum(peso[pais != "United States"]) / sum(peso),
            titulos = n_distinct(show_id), .groups = "drop")

continente_ano <- paises_longos %>%
  filter(between(ano_inclusao, 2016, 2020)) %>%
  group_by(ano_inclusao, continente) %>%
  summarise(peso = sum(peso), .groups = "drop_last") %>%
  mutate(participacao = peso / sum(peso)) %>% ungroup()

g_cont <- ggplot(continente_ano,
                 aes(x = ano_inclusao, y = participacao, color = continente, group = continente,
                     text = paste0(ano_inclusao, " · ", continente, ": ",
                                   formatar_pct(participacao)))) +
  geom_line(linewidth = 1.1) + geom_point(size = 2.6) +
  scale_color_manual(values = c("Américas" = cores_relatorio$azul,
                                "Ásia" = cores_relatorio$vermelho,
                                "Europa" = cores_relatorio$verde,
                                "África" = cores_relatorio$mostarda,
                                "Oceania" = cores_relatorio$roxo)) +
  scale_x_continuous(breaks = 2016:2020) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  labs(x = "Ano de inclusão", y = "Participação ponderada", color = "Continente")
interativo(g_cont, "A Ásia ganhou espaço até 2018; a África cresceu a partir de 2019",
           "Participação de cada continente entre os títulos incluídos em cada ano (contagem fracionada)")
fora <- function(ano) formatar_pct(internacional_ano$fora_eua[internacional_ano$ano_inclusao == ano])
cont <- function(ano, c) formatar_pct(continente_ano$participacao[
  continente_ano$ano_inclusao == ano & continente_ano$continente == c])
knitr::kable(
  internacional_ano %>% mutate(fora_eua = formatar_pct(fora_eua)),
  col.names = c("Ano de inclusão", "Produção fora dos EUA", "Títulos com país"),
  caption = "Participação ponderada de países que não os Estados Unidos"
)
Participação ponderada de países que não os Estados Unidos
Ano de inclusão Produção fora dos EUA Títulos com país
2015 32,9% 79
2016 57,1% 410
2017 63,9% 1123
2018 66,4% 1528
2019 59,3% 1856
2020 60,0% 1771

Em 2015, apenas 32,9% do peso dos títulos incluídos vinha de fora dos Estados Unidos. Já em 2016 esse valor saltou para 57,1% e chegou a 66,4% em 2018: desde 2016, a maior parte do que entra no catálogo é produzida fora dos EUA.

O avanço não foi uniforme entre continentes. A Ásia passou de 20,6% em 2016 para 38,3% em 2018, puxada principalmente pela Índia, e depois recuou. A África, quase ausente até 2018 (0,7%), chegou a 6,9% em 2020. A Europa perdeu participação relativa no período.

4.4 Quanto tempo separa lançamento e inclusão?

A variável intervalo_anos mede quantos anos se passaram entre o lançamento original e a entrada na Netflix. Casos negativos (14 registros, provavelmente séries com temporadas posteriores) ficam fora apenas deste gráfico.

intervalos_validos <- dados_limpos %>%
  filter(!is.na(intervalo_anos), intervalo_anos >= 0) %>%
  mutate(faixa = cut(intervalo_anos, breaks = c(-Inf, 1, 5, Inf),
                     labels = c("0 a 1 ano", "2 a 5 anos", "6 anos ou mais")))
faixas <- intervalos_validos %>%
  count(tipo, faixa) %>% group_by(tipo) %>%
  mutate(participacao = n / sum(n)) %>% ungroup()

g_intervalo <- ggplot(faixas, aes(x = tipo, y = participacao, fill = faixa,
                                 text = paste0(tipo, " · ", faixa, ": ",
                                               formatar_pct(participacao)))) +
  geom_col() +
  scale_fill_manual(values = c("0 a 1 ano" = cores_relatorio$azul,
                               "2 a 5 anos" = cores_relatorio$verde,
                               "6 anos ou mais" = cores_relatorio$mostarda)) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  labs(x = NULL, y = "Participação em cada tipo", fill = "Intervalo")
interativo(g_intervalo, "Séries chegam à plataforma mais perto do lançamento",
           "Anos entre o lançamento e a inclusão (somente intervalos não negativos)")
medianas <- intervalos_validos %>%
  group_by(tipo) %>%
  summarise(n = n(), mediana = median(intervalo_anos),
            ate_um = mean(intervalo_anos <= 1), .groups = "drop")
knitr::kable(medianas %>% mutate(ate_um = formatar_pct(ate_um)),
             col.names = c("Tipo", "Títulos analisados", "Mediana (anos)", "Até 1 ano"),
             caption = "Intervalo entre lançamento e inclusão")
Intervalo entre lançamento e inclusão
Tipo Títulos analisados Mediana (anos) Até 1 ano
Filme 6126 2 49,6%
Série 2653 0 67,3%

67,3% das séries entram na plataforma em até um ano após o lançamento, contra 49,6% dos filmes. As séries tendem a ser lançamentos recentes ou produções próprias; os filmes incluem muitos títulos de acervo licenciados anos depois.

4.5 Quais gêneros predominam em filmes e em séries?

Com o dicionário de gêneros, Dramas e TV Dramas passam a ser o mesmo grupo, o que permite comparar filmes e séries lado a lado. Rótulos de origem (como “Internacional”) e genéricos ficam fora desta comparação.

titulos_por_tipo <- dados_limpos %>% count(tipo, name = "total_tipo")
generos_tipo <- categorias_longas %>%
  filter(natureza == "Gênero") %>%
  distinct(show_id, tipo, genero) %>%
  count(tipo, genero) %>%
  left_join(titulos_por_tipo, by = "tipo") %>%
  mutate(participacao = n / total_tipo)
top_generos <- generos_tipo %>%
  group_by(genero) %>% summarise(total = sum(n), .groups = "drop") %>%
  slice_max(total, n = 10) %>% pull(genero)

g_generos <- generos_tipo %>%
  filter(genero %in% top_generos) %>%
  mutate(genero = factor(genero, levels = rev(top_generos))) %>%
  ggplot(aes(x = genero, y = participacao, fill = tipo,
             text = paste0(genero, " · ", tipo, ": ", formatar_pct(participacao),
                           " dos títulos do tipo (", n, ")"))) +
  geom_col(position = position_dodge(width = .8), width = .75) +
  coord_flip() +
  scale_fill_manual(values = cores_tipo) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  labs(x = NULL, y = "% dos títulos do tipo", fill = "Tipo")
interativo(g_generos, "Drama e comédia dominam; crime e reality são marcas das séries",
           "Dez grupos de gênero mais frequentes, em % dos filmes e das séries")
pct_gen <- function(g, t) formatar_pct(generos_tipo$participacao[
  generos_tipo$genero == g & generos_tipo$tipo == t])

Drama está em 39,6% dos filmes e 28,5% das séries. Já o perfil das séries tem marcas próprias: crime aparece em 17,6% delas, e reality shows em 9,5%, grupos praticamente exclusivos da TV. Nos filmes, ação e aventura (14,0%) e o rótulo de independente têm peso maior.

4.6 Quais países aparecem nos registros?

contribuicoes <- paises_longos %>%
  group_by(pais, pais_pt, continente) %>%
  summarise(titulos_associados = n_distinct(show_id),
            titulos_equivalentes = sum(peso), .groups = "drop") %>%
  mutate(participacao = titulos_equivalentes / sum(titulos_equivalentes)) %>%
  arrange(desc(titulos_equivalentes))
top_paises <- slice_head(contribuicoes, n = 10)

g_paises <- ggplot(top_paises, aes(x = reorder(pais_pt, titulos_equivalentes),
                                  y = titulos_equivalentes, fill = continente,
                                  text = paste0(pais_pt, ": ", formatar_n(round(titulos_equivalentes)),
                                                " títulos equivalentes"))) +
  geom_col() + coord_flip() +
  scale_fill_manual(values = c("Américas" = cores_relatorio$azul,
                               "Ásia" = cores_relatorio$vermelho,
                               "Europa" = cores_relatorio$verde)) +
  labs(x = NULL, y = "Títulos equivalentes (ponderados)", fill = "Continente")
interativo(g_paises, "Estados Unidos e Índia lideram os países de produção",
           "Dez países com maior contribuição, com contagem fracionada")
knitr::kable(top_paises %>% select(-pais) %>%
               mutate(titulos_equivalentes = round(titulos_equivalentes, 1),
                      participacao = formatar_pct(participacao)),
             col.names = c("País", "Continente", "Títulos associados",
                           "Títulos equivalentes", "Participação ponderada"),
             caption = "Dez países com maior contribuição ponderada")
Dez países com maior contribuição ponderada
País Continente Títulos associados Títulos equivalentes Participação ponderada
EUA Américas 3689 3187.4 40,0%
Índia Ásia 1046 1003.2 12,6%
Reino Unido Europa 806 570.5 7,2%
Canadá Américas 445 288.1 3,6%
Japão Ásia 318 273.2 3,4%
França Europa 393 222.9 2,8%
Coreia do Sul Ásia 231 211.8 2,7%
Espanha Europa 232 178.9 2,2%
México Américas 169 135.5 1,7%
Alemanha Europa 226 122.1 1,5%

Os Estados Unidos respondem por 40,0% do peso total, e a Índia, segunda colocada, por 12,6%. Juntos, os dez primeiros somam 77,7%. Os números consideram os 7.972 títulos com país informado.

4.7 Cada país traz um perfil de gênero diferente?

O mapa de calor cruza os oito países com mais títulos e os principais grupos de gênero. Cada célula mostra a porcentagem dos títulos daquele país que pertencem ao gênero; como um título pode ter vários gêneros, as linhas não somam 100%.

top8 <- head(contribuicoes$pais, 8)
generos_mapa <- c("Drama", "Comédia", "Ação e aventura", "Documentário", "Romance",
                  "Infantil e família", "Suspense", "Terror", "Anime", "Crime")

titulos_pais <- paises_longos %>% filter(pais %in% top8) %>%
  distinct(show_id, pais) %>% count(pais, name = "total_pais")
mapa <- paises_longos %>%
  filter(pais %in% top8) %>% distinct(show_id, pais) %>%
  inner_join(categorias_longas %>% filter(genero %in% generos_mapa) %>%
               distinct(show_id, genero), by = "show_id") %>%
  count(pais, genero) %>%
  tidyr::complete(pais = top8, genero = generos_mapa, fill = list(n = 0)) %>%
  left_join(titulos_pais, by = "pais") %>%
  mutate(pct = n / total_pais,
         pais_pt = factor(tabela_continentes$pais_pt[match(pais, tabela_continentes$pais)],
                          levels = rev(tabela_continentes$pais_pt[match(top8, tabela_continentes$pais)])),
         genero = factor(genero, levels = generos_mapa))

# Gráfico estático (ggplot puro) para variar a forma de apresentação.
ggplot(mapa, aes(x = genero, y = pais_pt, fill = pct)) +
  geom_tile(color = "white", linewidth = .8) +
  geom_text(aes(label = formatC(100 * pct, format = "f", digits = 0)),
            size = 3.4, color = ifelse(mapa$pct > .35, "white", cores_relatorio$texto)) +
  scale_fill_gradient(low = "#F2F6FA", high = cores_relatorio$marinho,
                      labels = scales::percent_format(accuracy = 1)) +
  labs(title = "O perfil de gênero muda bastante de um país para outro",
       subtitle = "% dos títulos de cada país associados a cada grupo de gênero",
       x = NULL, y = NULL, fill = "% do país") +
  theme(axis.text.x = element_text(angle = 35, hjust = 1), panel.grid = element_blank())

pct_mapa <- function(p, g) formatar_pct(mapa$pct[mapa$pais == p & mapa$genero == g], 0)

O cruzamento revela especializações que nenhuma coluna mostra sozinha. O Japão concentra anime: 64% dos seus títulos. A Índia é fortemente voltada a drama (66%), enquanto a Coreia do Sul tem a maior presença de romance entre os oito países (34%). O Reino Unido lidera em documentários (27%) e o Canadá em infantil e família (32%), dois gêneros quase ausentes entre os títulos indianos (3% e 4%).

4.8 Onde o Brasil aparece?

brasil <- dados_limpos %>%
  filter(show_id %in% paises_longos$show_id[paises_longos$pais == "Brazil"])
geral_series <- mean(dados_limpos$tipo == "Série")

generos_brasil <- categorias_longas %>%
  filter(show_id %in% brasil$show_id, natureza == "Gênero") %>%
  distinct(show_id, genero) %>% count(genero, sort = TRUE) %>%
  mutate(pct = n / nrow(brasil)) %>% slice_head(n = 5)

brasil_ano <- brasil %>% filter(!is.na(ano_inclusao)) %>% count(ano_inclusao)

knitr::kable(generos_brasil %>% mutate(pct = formatar_pct(pct)),
             col.names = c("Gênero", "Títulos brasileiros", "% dos títulos brasileiros"),
             caption = "Cinco gêneros mais frequentes entre títulos com participação brasileira")
Cinco gêneros mais frequentes entre títulos com participação brasileira
Gênero Títulos brasileiros % dos títulos brasileiros
Drama 37 38,1%
Comédia 26 26,8%
Documentário 18 18,6%
Independente 12 12,4%
Infantil e família 10 10,3%
g_brasil <- ggplot(brasil_ano, aes(x = factor(ano_inclusao), y = n,
                                   text = paste0(ano_inclusao, ": ", n, " títulos"))) +
  geom_col(fill = cores_relatorio$verde, width = .65) +
  labs(x = "Ano de inclusão", y = "Títulos com participação brasileira")
interativo(g_brasil, "As inclusões de títulos brasileiros cresceram até 2020",
           "Títulos com o Brasil entre os países de produção, por ano de inclusão (2021 até setembro)")

O catálogo tem 97 títulos com participação brasileira, 1,2% dos títulos com país informado. As séries são 32,0% desse grupo, acima da média geral de 30,4%. O gênero mais comum é drama, presente em 38,1% dos títulos. As inclusões saltaram de 2 em 2016 para 27 em 2020, acompanhando a internacionalização vista acima.

4.9 Duração dos filmes e número de temporadas

Minutos e temporadas são unidades diferentes, por isso cada tipo é analisado separadamente.

filmes <- dados_limpos %>% filter(tipo == "Filme")
series <- dados_limpos %>% filter(tipo == "Série")

g_filmes <- ggplot(filmes, aes(x = minutos)) +
  geom_histogram(binwidth = 10, boundary = 0, fill = cores_relatorio$azul, color = "white") +
  coord_cartesian(xlim = c(0, 240)) +
  labs(x = "Duração em minutos", y = "Quantidade de filmes")
interativo(g_filmes, "A maioria dos filmes dura entre 80 e 130 minutos",
           "Distribuição da duração (zoom até 240 minutos; nenhum dado foi removido)",
           tooltip = c("x", "y"))
contagem_temporadas <- series %>%
  mutate(grupo = factor(if_else(temporadas >= 6, "6 ou mais", as.character(temporadas)),
                        levels = c(as.character(1:5), "6 ou mais"))) %>%
  count(grupo)

g_temporadas <- ggplot(contagem_temporadas,
                       aes(x = grupo, y = n, text = paste0(grupo, ": ", n, " séries"))) +
  geom_col(fill = cores_relatorio$mostarda) +
  labs(x = "Temporadas", y = "Quantidade de séries")
interativo(g_temporadas, "Duas em cada três séries têm uma única temporada",
           "Número de temporadas informado no cadastro")

A duração mediana dos filmes é 98 minutos, e 71,3% deles ficam entre 80 e 130 minutos. Entre as séries, 67,0% têm apenas uma temporada, o que combina com o curto intervalo entre lançamento e inclusão: muitas séries entram no catálogo ainda novas.

4.10 Faixa de público por tipo

A variável faixa_publico, criada no pré-processamento, coloca os códigos de cinema e TV numa mesma escala.

publico <- dados_limpos %>%
  count(tipo, faixa_publico) %>%
  group_by(tipo) %>% mutate(participacao = n / sum(n)) %>% ungroup()

g_publico <- ggplot(publico, aes(x = tipo, y = participacao, fill = faixa_publico,
                                 text = paste0(tipo, " · ", faixa_publico, ": ",
                                               formatar_pct(participacao)))) +
  geom_col(width = .6) +
  scale_fill_manual(values = c("Infantil e livre" = cores_relatorio$verde,
                               "Orientação dos pais" = cores_relatorio$azul,
                               "Adolescente" = cores_relatorio$mostarda,
                               "Adulto" = cores_relatorio$vermelho,
                               "Não classificado" = "#B8C2CC")) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  labs(x = NULL, y = "Participação em cada tipo", fill = "Faixa de público")
interativo(g_publico, "Conteúdo adulto é o maior grupo em filmes e séries",
           "Classificação indicativa agrupada em faixas de público")
pct_pub <- function(f, t) formatar_pct(publico$participacao[
  publico$faixa_publico == f & publico$tipo == t])

O conteúdo adulto responde por 46,7% dos filmes e 42,8% das séries. O infantil e livre tem peso maior nas séries (17,4%) do que nos filmes (7,2%), refletindo o volume de desenhos e programas infantis em formato seriado.

4.11 O que as sinopses acrescentam?

A coluna description é texto livre. Separamos as sinopses em palavras, convertemos para minúsculas e removemos palavras funcionais do inglês (stop words).

palavras <- dados_limpos %>%
  select(show_id, tipo, description) %>%
  filter(!is.na(description)) %>%
  tidytext::unnest_tokens(palavra, description) %>%
  filter(stringr::str_length(palavra) >= 4,
         !stringr::str_detect(palavra, "^[0-9]+$")) %>%
  anti_join(tidytext::stop_words, by = c("palavra" = "word"))
mais_frequentes <- palavras %>% count(palavra, sort = TRUE) %>% slice_head(n = 12)

g_palavras <- ggplot(mais_frequentes, aes(x = reorder(palavra, n), y = n,
                                         text = paste0(palavra, ": ", n, " ocorrências"))) +
  geom_col(fill = cores_relatorio$verde) + coord_flip() +
  labs(x = NULL, y = "Ocorrências nas sinopses")
interativo(g_palavras, "Vida, família e amor são os temas mais citados nas sinopses",
           "12 palavras mais frequentes, após remover palavras funcionais do inglês")

Após os filtros restam 105.071 ocorrências de 18.580 palavras distintas. Termos como life, family, love e friends mostram que as sinopses vendem os títulos pelas relações humanas, mais do que pela ação ou pelo cenário. A sinopse média tem 24 palavras, um formato curto e padronizado.

5 Conclusões

5.1 Resposta ao problema

A pergunta era o que o cadastro revela sobre a composição, a internacionalização e a diversidade do catálogo da Netflix. A resposta tem três partes:

  • Composição: filmes são 69,6% dos 8.803 títulos, mas as séries chegam mais novas: 67,3% entram em até um ano após o lançamento.
  • Internacionalização: desde 2016, a maior parte do que entra no catálogo vem de fora dos EUA (66,4% em 2018). A Ásia liderou esse movimento até 2018, e a África cresceu depois.
  • Diversidade: cada país traz um perfil próprio (anime no Japão, drama na Índia, romance na Coreia do Sul, documentários no Reino Unido), e o Brasil acompanha a tendência de crescimento, com peso de séries acima da média.

5.2 Como o problema foi abordado

Os dados vieram de um único CSV público com 8.807 registros. O pré-processamento padronizou espaços em 32 células, corrigiu 3 durações gravadas na coluna errada, removeu 4 duplicatas que só diferiam em maiúsculas, minúsculas ou espaços, unificou a classificação indicativa em faixas de público e converteu datas e durações. Países e categorias foram separados em formato longo e cruzados com duas tabelas auxiliares (continentes e dicionário de gêneros). A exploração combinou gráficos interativos, um mapa de calor estático, tabelas e análise de texto.

5.3 Implicações para os interessados

Para pesquisadores de mídia, os dados mostram que a expansão internacional da Netflix aparece no catálogo, e não só no número de assinantes: a origem dos títulos mudou rapidamente entre 2015 e 2018. Para profissionais de curadoria e licenciamento, o mapa de calor indica em quais gêneros cada país é fornecedor relevante, e o recorte do Brasil mostra um mercado em crescimento com vocação para séries. Para quem trabalha com metadados, o projeto mostra como erros simples de cadastro (uma coluna trocada, um título duplicado com grafia diferente) alteram contagens se não forem tratados.

5.4 Limitações e próximos passos

  • O arquivo é um retrato de 2021: títulos removidos antes disso não aparecem, então as séries por ano descrevem o que permaneceu no catálogo, não tudo o que foi incluído.
  • 2021 está incompleto (até setembro).
  • Há títulos sem país, diretor ou elenco; o mapa geográfico considera só os títulos com país informado.
  • O ano de lançamento de séries pode se referir a temporadas diferentes, o que gera intervalos negativos.
  • O dicionário de gêneros envolve decisões de agrupamento que podem ser revistas.
  • Não há dados de audiência, então nada aqui mede popularidade.

Como continuação, seria possível comparar retratos do catálogo de anos diferentes para medir entradas e saídas, incluir dados de audiência (como as listas semanais de mais vistos da Netflix) e aplicar modelagem de tópicos às sinopses para identificar temas além da contagem de palavras.

6 Reprodutibilidade

O código está dividido em etapas (importação, diagnóstico, pré-processamento, tabelas auxiliares e análise), usa funções reutilizáveis (interativo(), tema_relatorio(), formatadores) e verifica os resultados com stopifnot(). Todo número citado no texto é calculado pelo código. Para reproduzir, coloque o .Rmd e o estilo_relatorio.css na pasta do projeto, o netflix_titles.csv em dados/ e clique em Knit. O código de cada etapa pode ser aberto pelo botão “Code”.

6.1 Fontes e referências