As conferências de processamento de linguagem natural dependem de
revisões por pares para selecionar artigos sólidos, relevantes e com
impacto potencial para a comunidade. A coleção PeerRead disponibiliza
essas avaliações com o objetivo de fomentar pesquisas sobre
transparência, vieses e qualidade do processo de revisão. Nesta análise,
utilizamos os dados da trilha CoNLL 2016, localizados
em data/conll_2016, para entender padrões de recomendação,
consistência entre revisores e características textuais dos
pareceres.
O objetivo é responder perguntas como: “Quais notas de recomendação são mais frequentes em cada partição (train/dev/test)?”, “Existe relação entre a confiança do revisor e a nota atribuída?”, “Como as rubricas analíticas (clareza, originalidade, impacto) se relacionam com a recomendação final?” e “Há diferenças léxicas entre comentários favoráveis e desfavoráveis?”.
Além de contextualizar o processo, também apresentamos exemplos visuais (trechos de pareceres e histogramas) para apoiar discussões sobre transparência e feedback para autores. O foco recai em métricas numéricas publicadas nos JSONs de revisão e na exploração textual dos comentários.
| Pacote | Propósito |
|---|---|
tidyverse |
Manipulação de dados (dplyr, tidyr), strings e visualizações básicas |
purrr |
Iteração funcional para varrer centenas de arquivos JSON |
jsonlite |
Leitura e parsing dos arquivos de revisão
(reviews/*.json) |
stringr |
Tratamento e contagem de palavras nos comentários |
tidytext |
Tokenização de texto e remoção de stopwords |
plotly |
Criação de gráficos interativos para explorar métricas |
rmdformats |
Tema Material Design aplicado ao relatório |
DT |
Tabelas interativas com filtragem e paginação |
lubridate |
Conversão de datas (quando presentes em metadados adicionais) |
scales |
Formatação de números e percentuais nos gráficos |
Os arquivos de avaliações por pares fazem parte da coleção PeerRead e
estão organizados localmente em data/conll_2016, separando
train, dev e test. Cada pasta
contém subdiretórios reviews/, pdfs/ e
parsed_pdfs/. Nesta análise usamos apenas os JSONs de
reviews/, que armazenam metadados de cada artigo e um array
de pareceres.
Cada arquivo corresponde a um artigo submetido ao CoNLL 2016. Os
campos principais incluem title, abstract,
id e uma lista reviews, onde cada elemento
descreve o comentário de um avaliador, notas para rubricas como
CLARITY, SOUNDNESS_CORRECTNESS,
ORIGINALITY, além da nota de recomendação
(RECOMMENDATION) e a confiança
(REVIEWER_CONFIDENCE).
reviews_dir <- "/home/paula/Documentos/mestrado/PeerRead/data/conll_2016"
available_splits <- c("train", "dev", "test")
`%||%` <- function(a, b) {
if (is.null(a) || length(a) == 0) {
return(b)
}
a
}
safe_numeric <- function(x) {
if (is.null(x) || length(x) == 0) {
return(NA_real_)
}
suppressWarnings(as.numeric(x))
}
read_review_file <- function(path, split) {
paper <- fromJSON(path, simplifyVector = FALSE)
review_entries <- paper$reviews
if (length(review_entries) == 0) {
return(tibble())
}
map_dfr(seq_along(review_entries), function(idx) {
review <- review_entries[[idx]]
tibble(
paper_id = paper$id %||% tools::file_path_sans_ext(basename(path)),
split = split,
title = paper$title %||% NA_character_,
abstract = paper$abstract %||% NA_character_,
review_index = idx,
comments = review$comments %||% NA_character_,
is_meta_review = isTRUE(review$is_meta_review),
recommendation = safe_numeric(review$RECOMMENDATION),
clarity = safe_numeric(review$CLARITY),
soundness = safe_numeric(review$SOUNDNESS_CORRECTNESS),
originality = safe_numeric(review$ORIGINALITY),
impact = safe_numeric(review$IMPACT),
substance = safe_numeric(review$SUBSTANCE),
meaningful_comparison = safe_numeric(review$MEANINGFUL_COMPARISON),
appropriateness = safe_numeric(review$APPROPRIATENESS),
replicability = safe_numeric(review$REPLICABILITY),
reviewer_confidence = safe_numeric(review$REVIEWER_CONFIDENCE),
presentation_format = review$PRESENTATION_FORMAT %||% NA_character_
)
})
}
reviews_raw <- map_dfr(
available_splits,
function(split) {
files <- list.files(
file.path(reviews_dir, split, "reviews"),
pattern = "\\.json$",
full.names = TRUE
)
if (length(files) == 0) {
return(tibble())
}
map_dfr(files, read_review_file, split = split)
}
)
reviews_tbl <- reviews_raw %>%
mutate(
split = factor(split, levels = available_splits),
comments = if_else(is.na(comments) | comments == "", NA_character_, comments),
comment_word_count = if_else(
!is.na(comments),
str_count(comments, boundary("word")),
NA_integer_
),
recommendation_bucket = case_when(
recommendation >= 4 ~ "Alta",
recommendation <= 2 ~ "Baixa",
TRUE ~ "Neutra"
),
recommendation_bucket = factor(
recommendation_bucket,
levels = c("Baixa", "Neutra", "Alta")
)
)
datatable(head(reviews_tbl, 25), options = list(pageLength = 5))
Os campos principais utilizados na análise são:
| Coluna | Descrição |
|---|---|
paper_id |
Identificador único do artigo no CoNLL 2016 |
split |
Partição de origem (train, dev ou
test) |
review_index |
Índice da revisão dentro do arquivo do artigo |
is_meta_review |
Indica se o texto é uma meta-review consolidada |
recommendation |
Nota de 1 (rejeitar) a 5 (aceitar fortemente) |
clarity, soundness,
originality, impact, substance,
meaningful_comparison, appropriateness,
replicability |
Rubricas específicas avaliadas em escala ordinal |
reviewer_confidence |
Confiança do avaliador em sua própria recomendação |
presentation_format |
Formato sugerido na conferência (oral, pôster, etc.) |
comments |
Texto livre do parecer |
comment_word_count |
Número de palavras detectado em cada comentário |
recommendation_bucket |
Agrupamento qualitativo (Baixa, Neutra, Alta) para facilitar comparações |
Para padronizar as análises:
reviews/ e expandimos o array de avaliações para obter uma
linha por combinação artigo–revisor.numeric, preservando
NA quando ausente ou inválida.comment_word_count) para aproximar o esforço
do avaliador e criamos a variável categórica
recommendation_bucket.paper_id para comparação entre trabalhos.paper_level <- reviews_tbl %>%
group_by(paper_id, split, title) %>%
summarise(
n_reviews = n(),
mean_recommendation = mean(recommendation, na.rm = TRUE),
mean_confidence = mean(reviewer_confidence, na.rm = TRUE),
mean_soundness = mean(soundness, na.rm = TRUE),
share_meta_review = mean(is_meta_review, na.rm = TRUE),
total_words = sum(comment_word_count, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(
mean_recommendation = round(mean_recommendation, 2),
mean_confidence = round(mean_confidence, 2),
mean_soundness = round(mean_soundness, 2),
share_meta_review = round(share_meta_review, 2)
)
datatable(head(paper_level, 20), options = list(pageLength = 5))
O objeto reviews_tbl consolidado contém uma linha por
parecer, enquanto paper_level resume estatísticas por
artigo. No total, carregamos 39 revisões.
Analisamos a dispersão das notas de recomendação em cada partição para verificar se há diferenças sistemáticas entre conjuntos que poderiam enviesar experimentos.
plot_ly(
data = reviews_tbl %>% filter(!is.na(recommendation)),
x = ~split,
y = ~recommendation,
color = ~split,
type = "box"
) %>%
layout(
title = "Distribuição das notas de recomendação por partição",
xaxis = list(title = "Partição"),
yaxis = list(title = "Recomendação (1-5)")
)
Calculamos a correlação de Pearson entre a nota de recomendação e cada rubrica para entender quais critérios mais influenciam o parecer final.
rubrica_corr <- reviews_tbl %>%
select(recommendation, clarity, soundness, originality, impact, substance, meaningful_comparison, appropriateness, replicability) %>%
pivot_longer(-recommendation, names_to = "rubrica", values_to = "valor") %>%
filter(!is.na(recommendation), !is.na(valor)) %>%
group_by(rubrica) %>%
summarise(correlacao = cor(recommendation, valor), .groups = "drop") %>%
arrange(desc(correlacao))
plot_ly(rubrica_corr,
x = ~correlacao,
y = ~reorder(rubrica, correlacao),
type = "bar",
orientation = "h",
marker = list(color = "#1f77b4")
) %>%
layout(
title = "Correlação entre rubricas e recomendação",
xaxis = list(title = "Correlação de Pearson"),
yaxis = list(title = "Rubrica")
)
Verificamos se revisores com maior confiança tendem a atribuir notas mais extremas e se há diferenças entre meta-reviews e revisões individuais.
plot_ly(
data = reviews_tbl %>% filter(!is.na(recommendation), !is.na(reviewer_confidence)),
x = ~reviewer_confidence,
y = ~recommendation,
color = ~is_meta_review,
type = "scatter",
mode = "markers",
marker = list(opacity = 0.6)
) %>%
layout(
title = "Confiança do revisor x recomendação",
xaxis = list(title = "Confiança"),
yaxis = list(title = "Recomendação"),
legend = list(title = list(text = "Meta review"))
)
Textos de pareceres foram tokenizados para identificar termos característicos de avaliações altas e baixas. Agrupamos palavras, removemos stopwords e destacamos os 10 termos mais frequentes por polaridade.
data("stop_words")
tokens_top <- reviews_tbl %>%
filter(!is.na(comments), !is.na(recommendation)) %>%
mutate(polaridade = if_else(recommendation >= 4, "Alta", "Baixa")) %>%
unnest_tokens(word, comments) %>%
anti_join(stop_words, by = "word") %>%
filter(str_detect(word, "^[a-zA-Z]+$")) %>%
count(polaridade, word, sort = TRUE) %>%
group_by(polaridade) %>%
slice_max(n, n = 10, with_ties = FALSE) %>%
ungroup() %>%
arrange(polaridade, n) %>%
mutate(label = paste(polaridade, word, sep = " | "),
label = factor(label, levels = label))
plot_ly(tokens_top,
x = ~n,
y = ~label,
color = ~polaridade,
type = "bar",
orientation = "h"
) %>%
layout(
title = "Principais termos por polaridade de recomendação",
xaxis = list(title = "Frequência"),
yaxis = list(title = "Polaridade | termo"),
legend = list(orientation = "h", x = 0.2, y = 1.1)
)
train,
dev e test, indicando que os splits preservam
a variedade de recomendações.IMPACT,
SOUNDNESS_CORRECTNESS e ORIGINALITY apresentam
as correlações mais fortes com a nota final, sugerindo que critérios
técnicos pesam mais que aspectos formais.Para reproduzir este relatório, basta garantir que os dados estejam
sob data/conll_2016 (ou data/conll_20216,
conforme convenção local) e renderizar o arquivo com
rmarkdown::render("analise2.Rmd") em um ambiente com os
pacotes listados.