Neste relatório, as questões 1 a 6 utilizam gráficos do R base e as questões 7 a 10 utilizam Plotly, conforme o enunciado. Cada aba reúne explicação, código e visualização. Os gráficos da questão 6 são produzidos a partir dos quatro arquivos de monitoramento anexados.

Questões

Questão 1 — Tempos de resposta (20 pontos)

Objetivo: comparar os tempos médios de resposta de diferentes quantidades de fogs em função do intervalo entre requisições. O gráfico de linhas mostra todas as séries. Os cinco gráficos de barras comparam cada quantidade de fog com a configuração sem fog, utilizando escala logarítmica e as duas cores especificadas no enunciado.

MRT_1F <- c(517.1468515630205, 85.13094142168089, 30.333207896694553,
            12.694776264558937, 3.3041601673945418, 1.1823111717498882,
            1.1892293502386786)
MRT_3F <- c(156.68929936163462, 11.540837783562276, 0.4512835621696538,
            0.4509797929766453, 0.4502068233039181, 0.4496185276300172,
            0.4543157082191288)
MRT_5F <- c(83.90319666471157, 0.3068151086494968, 0.30522314133037304,
            0.3072588968084928, 0.30655265997285697, 0.3055812715727718,
            0.3053297166713006)
MRT_10F <- c(29.55430642951759, 0.19832832665772515, 0.1971923924717474,
             0.19796648905716516, 0.19615594370806338, 0.2034569237883263,
             0.19617420889447737)
MRT_15F <- c(11.317736530583566, 0.167364215666193, 0.16172168266811013,
             0.16701085329580515, 0.1598052657153692, 0.1645934043532696,
             0.16216563797118075)
MRT_sem_F <- c(11.93430909937736, 0.6095414637034009, 0.6060645101029295,
               0.612167181646899, 0.6146761002685637, 0.6096747087200697,
               0.6125810476877268)
clock <- c(0.1, 0.5, 1, 1.5, 2, 2.5, 3)

series <- list("1 Fog" = MRT_1F, "3 Fogs" = MRT_3F,
               "5 Fogs" = MRT_5F, "10 Fogs" = MRT_10F,
               "15 Fogs" = MRT_15F, "Sem Fog" = MRT_sem_F)
cores_linha <- c("#424242", "#D79123", "#CEB01D",
                 "#4383B8", "#9B72BE", "#259D83")

# Primeiro gráfico ocupa as duas colunas; os demais ocupam células individuais.
graphics::layout(matrix(c(1, 1, 2, 3, 4, 5, 6, 0), ncol = 2, byrow = TRUE),
       heights = c(2.8, 2, 2, 2))
par(mar = c(4, 4.5, 3, 1))
plot(clock, MRT_1F, type = "b", pch = 4, lwd = 1.5,
     col = cores_linha[1], xlim = range(clock),
     ylim = c(0, max(MRT_1F) * 1.04),
     xlab = "Intervalo entre requisições (s)",
     ylab = "Tempo de resposta (s)",
     main = "Tempo de resposta por configuração")
for (i in 2:length(series)) {
  lines(clock, series[[i]], type = "b", pch = i,
        col = cores_linha[i], lwd = 1.5)
}
legend("topright", legend = names(series), col = cores_linha,
       lty = 1, pch = c(4, 2:6), bty = "n", cex = 0.82)
grid(col = "#EDEDED")

# Exatamente as duas cores exigidas e eixo Y logarítmico.
for (i in 1:5) {
  comparacao <- rbind("Sem Fog" = MRT_sem_F, series[[i]])
  rownames(comparacao) <- c("Sem Fog", names(series)[i])
  barplot(comparacao, beside = TRUE, log = "y",
          col = c("#E6E6E6", "#666666"), border = "#333333",
          names.arg = clock, ylim = c(0.1, 1000),
          xlab = "Intervalo (s)", ylab = "Resposta (s) — log",
          main = paste("Sem Fog vs.", names(series)[i]), cex.main = 0.95)
  legend("topright", legend = rownames(comparacao),
         fill = c("#E6E6E6", "#666666"), cex = 0.67, bty = "n")
}

graphics::layout(1)

Leitura dos resultados: tempos altos no primeiro intervalo tornam algumas curvas difíceis de distinguir no gráfico de linhas. Por isso, a escala logarítmica nos gráficos de barras ajuda a comparar configurações cujos tempos possuem ordens de grandeza muito diferentes.

Questão 2 — Qualidade da refeição por preço (5 pontos)

Objetivo: construir barras empilhadas para comparar as porcentagens de avaliações Good, Very Good e Excellent em cada faixa de preço. A matriz reproduz os números da tabela fornecida no enunciado (os totais podem variar em 0,1 ponto percentual por arredondamento).

qualidade <- matrix(
  c(53.8, 43.6, 2.6,
    33.9, 54.2, 11.9,
    2.6, 60.5, 36.8,
    0.0, 21.4, 78.6),
  nrow = 3, ncol = 4,
  dimnames = list(c("Good", "Very Good", "Excellent"),
                  c("$10–19", "$20–29", "$30–39", "$40–49"))
)
barplot(qualidade, beside = FALSE, col = c("#C6D8ED", "#5B91BF", "#234F78"),
        border = "white", ylim = c(0, 105),
        xlab = "Faixa de preço da refeição", ylab = "Avaliações (%)",
        main = "Qualidade da refeição por faixa de preço",
        legend.text = rownames(qualidade),
        args.legend = list(x = "topright", bty = "n", cex = 0.82))

Leitura dos resultados: nas refeições de preço mais elevado, aumenta a participação de avaliações Excellent, enquanto Good se torna menos frequente.

Questão 3 — Temperatura de maio (5 pontos)

Objetivo: selecionar o mês de maio de airquality, converter graus Fahrenheit para Celsius e apresentar um histograma de densidade com uma curva suavizada.

data(airquality)
maio <- subset(airquality, Month == 5)
temperatura_c <- (maio$Temp - 32) / 1.8
temperatura_c <- temperatura_c[!is.na(temperatura_c)]

hist(temperatura_c, probability = TRUE,
     col = "#73B6CC", border = "white", breaks = "Sturges",
     main = "Distribuição das temperaturas de maio",
     xlab = "Temperatura (°C)", ylab = "Densidade")
lines(density(temperatura_c), col = "#C9564C", lwd = 2.5)
rug(temperatura_c, col = "#555555")

Leitura dos resultados: probability=TRUE ajusta o histograma para a escala de densidade, permitindo sobrepor density() corretamente. A conversão usa a fórmula pedida: °C = (°F - 32)/1.8.

Questão 4 — Vendas por país (5 pontos)

Objetivo: apresentar o percentual das vendas de cada país em um gráfico de pizza, com fatias coloridas, porcentagens e legenda. Os dados de Sales.txt acompanham este relatório para permitir uma execução sem internet.

sales <- read.table("Sales.txt", header = TRUE)
vendas_pais <- aggregate(SALES ~ COUNTRY, data = sales, FUN = sum)
porcentagem <- 100 * vendas_pais$SALES / sum(vendas_pais$SALES)
rotulos <- paste0(round(porcentagem, 1), "%")
cores <- c("#3A87A6", "#E7A346", "#80A77C",
           "#9B86BD", "#D27C75", "#638FC4")
pie(vendas_pais$SALES, labels = rotulos,
    col = cores, border = "white", clockwise = TRUE,
    main = "Participação percentual nas vendas por país")
legend("topright", legend = vendas_pais$COUNTRY,
       fill = cores, bty = "n", cex = 0.82)

Leitura dos resultados: a participação de cada país é calculada dividindo suas vendas pelo total de vendas, multiplicado por 100. A legenda identifica os países representados pelas cores.

Questão 5 — Comparação de inseticidas (5 pontos)

Objetivo: mostrar as contagens de insetos para os seis inseticidas do dataset InsectSprays, em caixas amarelas e sem desenhar os pontos dos outliers, como solicita a questão.

data(InsectSprays)
boxplot(count ~ spray, data = InsectSprays,
        col = "yellow", outline = FALSE,
        main = "Contagens de insetos por inseticida",
        xlab = "Tipo de inseticida", ylab = "Número de insetos")

Leitura dos resultados: o boxplot compara a mediana, a variação e os quartis de cada tratamento. outline=FALSE oculta os símbolos dos valores discrepantes; não remove registros da base.

Questão 6 — Memória da máquina virtual (20 pontos)

Objetivo: ler os quatro arquivos de monitoramento, transformar currentTime em uma escala contínua de horas desde a primeira coleta, converter usedMemory para megabytes e organizar os quatro gráficos usando layout().

Arquivos necessários: monitoringCloudData_NONE.csv, monitoringCloudData_0.1.csv, monitoringCloudData_0.5.csv e monitoringCloudData_1.csv. Os arquivos devem ficar na mesma pasta do .Rmd antes de publicar. Como não foram incluídos no PDF, nenhum valor foi criado artificialmente.

arquivos <- c(
  "monitoringCloudData_NONE.csv",
  "monitoringCloudData_0.1.csv",
  "monitoringCloudData_0.5.csv",
  "monitoringCloudData_1.csv"
)
titulos <- c("Sem carga", "Carga de 0.1", "Carga de 0.5", "Carga de 1.0")

# Os horários dos CSVs incluem microssegundos.
# Um trecho do arquivo 0.5 tem DUAS datas no mesmo registro:
#   2020-01-31 2020-01-30 00:01:04.385180
# Nesses casos, testamos as duas datas possíveis e escolhemos
# aquela que mantém a sequência cronológica das coletas.
converter_tempo <- function(x) {
  texto <- trimws(as.character(x))
  padrao_duplo <- paste0(
    "^([0-9]{4}-[0-9]{2}-[0-9]{2}) ",
    "([0-9]{4}-[0-9]{2}-[0-9]{2}) ",
    "([0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]+)?)$"
  )
  duplicados <- grepl(padrao_duplo, texto, perl = TRUE)

  # Primeiro faz a conversão explícita, sem tentar adivinhar o formato.
  # Para os registros com data duplicada, usa temporariamente a 1ª data.
  texto_normalizado <- texto
  texto_normalizado[duplicados] <- sub(
    padrao_duplo, "\\1 \\3", texto[duplicados], perl = TRUE
  )
  formato <- "%Y-%m-%d %H:%M:%OS"
  tempo <- as.POSIXct(texto_normalizado, format = formato, tz = "UTC")

  # Decide, em cada registro malformado, qual data dá continuidade
  # ao registro anterior (e não uma volta de 24 horas).
  for (i in which(duplicados)) {
    partes <- regmatches(
      texto[i], regexec(padrao_duplo, texto[i], perl = TRUE)
    )[[1]]
    candidatos <- as.POSIXct(
      c(paste(partes[2], partes[4]), paste(partes[3], partes[4])),
      format = formato, tz = "UTC"
    )
    if (i > 1 && !is.na(tempo[i - 1])) {
      diferencas <- as.numeric(difftime(candidatos, tempo[i - 1], units = "secs"))
      validos <- which(is.finite(diferencas) & diferencas >= 0)
      if (length(validos) > 0) {
        mais_proximo <- validos[which.min(diferencas[validos])]
        tempo[i] <- candidatos[mais_proximo]
      }
    }
  }
  if (anyNA(tempo)) {
    stop("Horário inválido em currentTime: ", texto[which(is.na(tempo))[1]])
  }
  tempo
}

# Extrai o valor e a unidade da memória (sem descartar dados).
# Conversões exigidas pelo enunciado: 1 GB = 1024 MB; 1 TB = 1000000 MB.
converter_memoria_mb <- function(x) {
  texto <- toupper(gsub("\\s+", "", as.character(x)))
  valor_texto <- sub("^([0-9]+(?:[.,][0-9]+)?).*$", "\\1", texto, perl = TRUE)
  valor <- suppressWarnings(as.numeric(gsub(",", ".", valor_texto)))
  unidade <- sub("^[0-9]+(?:[.,][0-9]+)?", "", texto, perl = TRUE)
  fator <- c("B" = 1/1000000, "KB" = 1/1024,
             "MB" = 1, "GB" = 1024, "TB" = 1000000)
  if (anyNA(valor) || any(!unidade %in% names(fator))) {
    stop("Formato inválido na coluna usedMemory.")
  }
  valor * unname(fator[unidade])
}

preparar_serie <- function(arquivo) {
  dados <- read.csv(arquivo, stringsAsFactors = FALSE)
  stopifnot(all(c("currentTime", "usedMemory") %in% names(dados)))
  linhas_ajustadas <- sum(grepl(
    "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{4}-[0-9]{2}-[0-9]{2} ",
    dados$currentTime
  ))
  dados$tempo <- converter_tempo(dados$currentTime)
  dados$memoria_mb <- converter_memoria_mb(dados$usedMemory)
  dados <- dados[order(dados$tempo), ]
  dados$horas <- as.numeric(difftime(dados$tempo, min(dados$tempo),
                                     units = "hours"))
  attr(dados, "datas_corrigidas") <- linhas_ajustadas
  dados
}

faltantes <- arquivos[!file.exists(arquivos)]
if (length(faltantes) > 0) {
  stop(paste("Arquivos da Questão 6 não encontrados:",
             paste(faltantes, collapse = ", ")))
}

series_memoria <- lapply(arquivos, preparar_serie)

# Painel com os quatro gráficos, como no enunciado.
graphics::layout(matrix(1:4, nrow = 2, byrow = TRUE))
par(mar = c(4.5, 4.6, 3, 1))
for (i in seq_along(series_memoria)) {
  dados <- series_memoria[[i]]
  plot(dados$horas, dados$memoria_mb, type = "l", col = "#263746",
       lwd = 1.1, main = paste("Análise de memória —", titulos[i]),
       xlab = "Tempo desde o início (horas)",
       ylab = "Memória usada (MB)")
  grid(col = "#E4E8EB")
}

graphics::layout(1)

# Resumo da limpeza e transformação de unidades, com os dados reais.
resumo_6 <- data.frame(
  Condicao = titulos,
  Amostras = vapply(series_memoria, nrow, integer(1)),
  Datas_ajustadas = vapply(series_memoria, function(d)
    attr(d, "datas_corrigidas"), numeric(1)),
  Horas = round(vapply(series_memoria, function(d)
    max(d$horas), numeric(1)), 2),
  Minimo_MB = round(vapply(series_memoria, function(d)
    min(d$memoria_mb), numeric(1)), 2),
  Maximo_MB = round(vapply(series_memoria, function(d)
    max(d$memoria_mb), numeric(1)), 2)
)
cat("\n\n**Resumo dos arquivos processados:**\n\n")

Resumo dos arquivos processados:

print(knitr::kable(resumo_6, row.names = FALSE))
Condicao Amostras Datas_ajustadas Horas Minimo_MB Maximo_MB
Sem carga 4302 0 71.98 95.70 106.52
Carga de 0.1 4275 0 72.00 245.03 3584.00
Carga de 0.5 4303 194 72.00 257.46 1198.08
Carga de 1.0 4303 0 72.00 241.10 254.23

Leitura dos resultados: difftime() transforma os horários de coleta em tempo decorrido; a conversão mantém todas as amostras em MB. Foram corrigidos registros de data duplicada que impediam a conversão de horários. A tabela de diagnóstico logo acima informa quantos registros foram corrigidos em cada arquivo.

Questão 7 — Dez países com mais conteúdos Netflix (10 pontos)

Objetivo: considerar apenas as linhas com um único país de origem, excluir países ausentes e construir um gráfico de pizza Plotly com os dez países mais frequentes.

Nota sobre a versão dos dados: o arquivo netflix_titles.csv enviado para esta atividade contém 7787 títulos, incluindo registros adicionados até 2021, diferentemente da descrição da base de 2019 no enunciado. Os gráficos das questões 7 a 10 usam o CSV presente nesta pasta, sem substituir ou inventar registros. Caso o professor forneça uma edição de 2019, substitua o arquivo e execute novamente o Knit.

if (file.exists("netflix_titles.csv")) {
  netflix <- read.csv("netflix_titles.csv", stringsAsFactors = FALSE,
                      na.strings = c("", "NA"), fileEncoding = "UTF-8")
} else {
  endereco <- paste0(
    "https://raw.githubusercontent.com/byui-cse/",
    "cse450-course/master/data/netflix_titles.csv"
  )
  netflix <- tryCatch(
    read.csv(endereco, stringsAsFactors = FALSE, na.strings = c("", "NA"),
             fileEncoding = "UTF-8"),
    error = function(e) NULL
  )
}

if (is.null(netflix)) {
  cat("Não foi possível ler a base Netflix. Baixe netflix_titles.csv e coloque ao lado do .Rmd.")
} else {
  pais <- trimws(netflix$country)
  um_pais <- !is.na(pais) & nzchar(pais) & !grepl(",", pais, fixed = TRUE)
  contagens <- sort(table(pais[um_pais]), decreasing = TRUE)
  dez_maiores <- head(contagens, 10)
  top_10 <- data.frame(Pais = names(dez_maiores),
                       Total = as.integer(dez_maiores))

  grafico_7 <- plot_ly(data = top_10, labels = ~Pais, values = ~Total,
                       type = "pie", hole = 0.2,
                       textinfo = "label+percent",
                       hovertemplate = "%{label}<br>%{value} conteúdos<br>%{percent}<extra></extra>") %>%
    layout(title = "Dez países com mais conteúdos — país único",
           showlegend = TRUE)
  grafico_7
}

Leitura dos resultados: uma produção com países separados por vírgula não entra nessa contagem. Cada título com um único país conta exatamente uma vez.

Questão 8 — Tabela Plotly dos dez países (10 pontos)

Objetivo: utilizar os mesmos dez países da questão 7 e mostrar as colunas País e Total de conteúdos em uma tabela Plotly. O cabeçalho deve ter fundo cinza, letras brancas, e as células devem estar centralizadas.

if (!exists("top_10")) {
  cat("Execute a Questão 7 com a base Netflix disponível.")
} else {
  tabela_8 <- plot_ly(
    type = "table",
    header = list(
      values = c("<b>País</b>", "<b>Total de conteúdos</b>"),
      align = c("center", "center"),
      fill = list(color = "#666666"),
      font = list(color = "white", size = 14),
      height = 36
    ),
    cells = list(
      values = list(top_10$Pais, top_10$Total),
      align = c("center", "center"),
      fill = list(color = "#F1F4F7"),
      font = list(color = "#263746", size = 12),
      height = 30
    )
  ) %>% layout(title = "Países com mais conteúdos na Netflix")
  tabela_8
}

Leitura dos resultados: a tabela mostra os números absolutos associados às fatias do gráfico da aba anterior.

Questão 9 — Conteúdos por década (10 pontos)

Objetivo: contar os títulos da base Netflix por década de lançamento (release_year), distinguindo séries (TV Show, linha azul) e filmes (Movie, linha amarela) no Plotly. Por exemplo, anos 2000 a 2009 pertencem à década 2000.

if (is.null(netflix)) {
  cat("A base Netflix não foi carregada na Questão 7.")
} else {
  validos <- netflix[!is.na(netflix$release_year) &
                      netflix$type %in% c("TV Show", "Movie"), ]
  validos$decada <- floor(as.numeric(validos$release_year) / 10) * 10
  decadas <- sort(unique(validos$decada))
  qt_series <- sapply(decadas, function(d) sum(validos$decada == d &
                                                validos$type == "TV Show"))
  qt_filmes <- sapply(decadas, function(d) sum(validos$decada == d &
                                                validos$type == "Movie"))

  grafico_9 <- plot_ly(x = decadas, y = qt_series,
                       type = "scatter", mode = "lines+markers",
                       name = "Séries", line = list(color = "#277DB4", width = 3)) %>%
    add_trace(x = decadas, y = qt_filmes,
              type = "scatter", mode = "lines+markers",
              name = "Filmes", line = list(color = "#E6B526", width = 3)) %>%
    layout(title = "Conteúdos Netflix por década de lançamento",
           xaxis = list(title = "Década", tickmode = "array", tickvals = decadas),
           yaxis = list(title = "Quantidade de conteúdos"),
           legend = list(orientation = "h"))
  grafico_9
}

Leitura dos resultados: a década considera o ano de lançamento do título, não o ano em que ele entrou no catálogo Netflix.

Questão 10 — Gêneros de filmes, 2000–2010 (10 pontos)

Objetivo: mostrar, em cada ano de 2000 a 2010 (inclusive), barras lado a lado para os filmes dos gêneros Dramas, Action & Adventure e Comedies. Se listed_in contiver vários gêneros, será considerado apenas o primeiro.

if (is.null(netflix)) {
  cat("A base Netflix não foi carregada na Questão 7.")
} else {
  filmes <- netflix[netflix$type == "Movie" &
                      !is.na(netflix$release_year) &
                      netflix$release_year >= 2000 &
                      netflix$release_year <= 2010, ]
  filmes$primeiro_genero <- trimws(sub(",.*$", "", filmes$listed_in))
  anos <- 2000:2010
  generos <- c("Dramas", "Action & Adventure", "Comedies")
  quantidades <- sapply(generos, function(g) {
    sapply(anos, function(a) {
      sum(filmes$release_year == a & filmes$primeiro_genero == g,
          na.rm = TRUE)
    })
  })

  grafico_10 <- plot_ly(x = as.character(anos), y = quantidades[, "Dramas"],
                        type = "bar", name = "Dramas",
                        marker = list(color = "#287DB0")) %>%
    add_bars(x = as.character(anos), y = quantidades[, "Action & Adventure"],
             name = "Action & Adventure", marker = list(color = "#E69C34")) %>%
    add_bars(x = as.character(anos), y = quantidades[, "Comedies"],
             name = "Comedies", marker = list(color = "#36A66A")) %>%
    layout(title = "Filmes por gênero e ano de lançamento (2000–2010)",
           xaxis = list(title = "Ano de lançamento"),
           yaxis = list(title = "Quantidade de filmes"),
           barmode = "group")
  grafico_10
}

Leitura dos resultados: filtrar somente type == "Movie" evita incluir séries. A extração anterior à primeira vírgula de listed_in faz com que cada filme entre em, no máximo, um dos três gêneros mostrados.


Fontes dos dados: enunciado Exercício 12 — Visualização de Dados (fornecido pelo professor); conjuntos airquality e InsectSprays do R; Sales.txt de Training Course Material; base Netflix enviada pelo aluno (7787 registros; edição mais recente do que a versão de 2019 citada no PDF). Os arquivos de monitoramento da questão 6 acompanham o pacote corrigido.