Load SOPHIS tables and needed packages.
NB: In the following, we always use the relational version, not the graph version!
Remember to change the folder_path variable to the path
of the folder where you download the .csv files.
library(tidyverse)
library(reactable)
library(ggpmisc)
library(scales)
library(stringr)
## Change this
folder_path <- "data"
csv_files <- list.files(path = folder_path, pattern = "\\.csv$", full.names = TRUE)
file_names <- tools::file_path_sans_ext(basename(csv_files))
for (i in seq_along(csv_files)) {
df <- read.csv(csv_files[i])
assign(file_names[i], df, envir = .GlobalEnv)
}
remove(df)
article <- article %>%
mutate(Ack_Text = ifelse(Ack_Text == "", NA, Ack_Text))
Articles by journal
articles_by_journal <- article %>%
group_by(Journal_Abb) %>%
summarise(n_articles = n_distinct(UT)) %>%
mutate(perc_articles = n_articles/sum(n_articles))
articles_by_journal %>%
ggplot(aes(x = reorder(Journal_Abb, perc_articles), y = perc_articles))+
geom_col(fill = "steelblue")+
geom_text(aes(label = round(perc_articles * 100,1)), hjust = -0.2)+
scale_y_continuous(
labels = scales::percent_format(),
limits = c(0, 1)
) +
labs(x = "", y = "Proportion of articles in the corpus") +
coord_flip()+
theme_minimal()
Articles by publication year:
articles_by_year <- article %>%
group_by(Pub_Year) %>%
summarise(n_articles = n_distinct(UT)) %>%
mutate(perc_articles = n_articles/sum(n_articles))
articles_by_year %>%
ggplot(aes(x = Pub_Year, y = n_articles))+
geom_col(fill = "steelblue")+
geom_text(aes(label = n_articles), vjust = 1.2, color = "white") +
labs(x = "Publication Year", y = "Number of articles") +
theme_minimal()
Number of distinct acknowledgees, number of distinct authors, number of actors that are 1) both authors and acknowledgees, 2) acknowledgees only, 3) authors only:
n_distinct(mention$H_ID) - 1
## [1] 8952
n_distinct(authorship$H_ID)
## [1] 4380
n_distinct(intersect(mention$H_ID, authorship$H_ID))
## [1] 2456
n_distinct(mention$H_ID) - 1 - n_distinct(intersect(mention$H_ID, authorship$H_ID))
## [1] 6496
n_distinct(authorship$H_ID) - n_distinct(intersect(mention$H_ID, authorship$H_ID))
## [1] 1924
Number of articles with acknowledgements:
ack_freq <- sum(!is.na(article$Ack_Text))
ack_freq
## [1] 5376
Acknowledgements intensity (proportion of articles with acknowledgements):
ack_int <- round(100-sum(is.na(article$Ack_Text))/n_distinct(article$UT)*100, 2)
ack_int
## [1] 78.76
Personal acknowledgements intensity (proportion of articles thanking at least one person):
given_mentions_by_article <- mention %>%
filter(Ent_Category == "PERSON") %>%
group_by(UT) %>%
summarise(n_mentions = n_distinct(H_ID)) %>%
right_join(article, by = "UT") %>%
mutate(n_mentions = replace_na(n_mentions, 0))
articles_with_personal_ackgees = given_mentions_by_article %>% filter(n_mentions > 0)
ack_pers_int <- round(n_distinct(articles_with_personal_ackgees$UT)/n_distinct(article$UT)*100, 2)
ack_pers_int
## [1] 68.22
Average number of acknowledgees per paper (including only papers with personal acknowledgements):
mention %>%
filter(!is.na(H_ID)) %>%
group_by(UT) %>%
summarise(n_ackgees = n_distinct(H_ID)) %>%
summarise(avg_n_ackgees = round(mean(n_ackgees),1),
sd_n_ackgees = round(sd(n_ackgees),1),
median_n_ackgees = median(n_ackgees)) %>%
reactable()
Distribution of acknowledgement length (in words):
article %>%
mutate(ack_word_len = str_count(Ack_Text, "\\S+")) %>%
filter(!is.na(ack_word_len)) %>%
ggplot(aes(x=ack_word_len)) +
geom_histogram(fill = "steelblue")+
geom_vline(aes(xintercept = mean(ack_word_len)), color = "red")+
geom_vline(aes(xintercept = median(ack_word_len)), color = "blue")+
theme_minimal()+
labs(y = "n articles", x = "Acknowledgement length (in words)")
article %>%
mutate(ack_word_len = str_count(Ack_Text, "\\S+")) %>%
filter(!is.na(ack_word_len)) %>%
summarise(mean_len = round(mean(ack_word_len), 2),
sd_len = round(sd(ack_word_len),2),
min_len = min(ack_word_len),
max_len = max(ack_word_len),
median_len = median(ack_word_len)) %>%
reactable()
Out-going mentions per article. We count how many distinct acknowledgees are thanked in each paper and visualize the distribution of the variable
given_mentions_by_article %>%
filter(n_mentions > 1) %>%
ggplot(aes(x = n_mentions)) +
geom_histogram(binwidth = 1, fill = "steelblue") +
theme_minimal() +
labs(x = "Mentions given", y = "Number of articles")
given_mentions_by_article %>%
ggplot(aes(x = n_mentions)) +
stat_ecdf() +
theme_minimal() +
scale_y_continuous(labels = scales::percent_format())+
labs(x = "Mentions given", y = "Proportion of actors")
Personal acknowledgement intensity by sub-discipline. The average personal intensity is \(68.2\%\)
articles_by_area <- article %>%
left_join(clustering, by = "UT") %>%
filter(Resolution == "Intermediate") %>%
left_join(cluster, by = c("Resolution", "Cluster")) %>%
group_by(Cluster_Label) %>%
summarise(n_articles = n_distinct(UT))
number_personal_ack <- given_mentions_by_article %>%
filter(n_mentions > 0) %>%
left_join(clustering, by = "UT") %>%
filter(Resolution == "Intermediate") %>%
left_join(cluster, by = c("Resolution", "Cluster")) %>%
group_by(Cluster_Label) %>%
summarise(n_articles_with_ack = n_distinct(UT)) %>%
left_join(articles_by_area, by = "Cluster_Label") %>%
mutate(perc_pers_ack = n_articles_with_ack/n_articles)
avg_pers_ack_intensity = n_distinct(articles_with_personal_ackgees$UT)/n_distinct(article$UT)
avg_pers_ack_intensity
## [1] 0.6822444
number_personal_ack %>%
ggplot(aes(x = Cluster_Label, y = perc_pers_ack)) +
geom_col(fill = "steelblue") +
geom_text(aes(label=round(perc_pers_ack*100, 1)), hjust = -0.2)+
theme_classic() +
geom_hline(aes(yintercept = avg_pers_ack_intensity))+
scale_y_continuous(
labels = scales::percent_format(),
limits = c(0, 1)
) +
labs(x = "Subdiscipline", y = "Proportion of articles with acknowledgments") +
coord_flip()
Personal acknowledgement intensity by journal:
number_personal_ack_by_journal <- given_mentions_by_article %>%
filter(n_mentions > 0) %>%
group_by(Journal_Abb)%>%
summarise(n_articles_with_ack = n_distinct(UT)) %>%
left_join(articles_by_journal, by = "Journal_Abb") %>%
mutate(perc_pers_ack = n_articles_with_ack/n_articles)
number_personal_ack_by_journal %>%
ggplot(aes(x = Journal_Abb, y = perc_pers_ack)) +
geom_col(fill = "steelblue") +
geom_text(aes(label=round(perc_pers_ack*100, 1)), hjust = -0.2)+
theme_classic() +
geom_hline(aes(yintercept = avg_pers_ack_intensity))+
scale_y_continuous(
labels = scales::percent_format(),
limits = c(0, 1)
) +
labs(x = "Journal", y = "Proportion of articles with acknowledgments") +
coord_flip()
Number and proportion of articles mentioning at least one funder (and proportion of articles with acknowledgments):
n_distinct(funding$UT)
## [1] 1990
perc_fund_articles <- round(n_distinct(funding$UT)/n_distinct(article$UT)*100, 2)
perc_fund_articles
## [1] 29.15
perc_fund_article_2 <- round(n_distinct(funding$UT)/sum(!is.na(article$Ack_Text))*100, 2)
perc_fund_article_2
## [1] 37.02
Number of funding bodies and funding countries, followed by top funding bodies and top funding countries:
n_distinct(funder$Funder_ID)
## [1] 515
n_distinct(funder$Funder_Country)
## [1] 51
funding %>%
group_by(Funder_ID) %>%
summarise(n_articles = n_distinct(UT)) %>%
mutate(prop_articles = round(n_articles/n_distinct(article$UT)*100, 2)) %>%
left_join(funder, by = "Funder_ID") %>%
select(Funder_Label, Funder_Country_ISO, n_articles, prop_articles) %>%
arrange(desc(n_articles)) %>%
reactable(searchable = TRUE)
funding %>%
left_join(funder, by = "Funder_ID") %>%
group_by(Funder_Country) %>%
summarise(n_articles = n_distinct(UT)) %>%
mutate(prop_articles = round(n_articles/n_distinct(article$UT)*100, 2)) %>%
select(Funder_Country, n_articles, prop_articles) %>%
arrange(desc(n_articles)) %>%
reactable(searchable = TRUE, resizable = TRUE, filterable = TRUE)
The Sankey network shows inter-cluster relationships. The nodes are the clusters, the links represent the number of articles shared across clusters.
library(networkD3)
clustering_df <- clustering %>%
left_join(cluster, by = c("Cluster" = "Cluster", "Resolution" = "Resolution")) %>%
select(UT, Resolution, Cluster_Label) %>%
pivot_wider(names_from = Resolution, values_from = Cluster_Label)
all_cluster_labels <- unique(unlist(clustering_df[ , -1])) # exclude 'DI'
sankey_nodes <- data.frame(name = all_cluster_labels, stringsAsFactors = FALSE)
resolution_pairs <- list(
c("Coarse", "Intermediate"),
c("Intermediate", "Fine"))
sankey_links_df <- data.frame()
for (pair in resolution_pairs) {
res_from <- pair[1]
res_to <- pair[2]
temp_links <- clustering_df %>%
filter(!is.na(.data[[res_from]]), !is.na(.data[[res_to]])) %>%
group_by(source = .data[[res_from]], target = .data[[res_to]]) %>%
summarise(value = n()) %>%
mutate(
source = match(source, sankey_nodes$name) - 1,
target = match(target, sankey_nodes$name) - 1
)
sankey_links_df <- bind_rows(sankey_links_df, temp_links)
}
sankey_plot <- sankeyNetwork(
Links = sankey_links_df,
Nodes = sankey_nodes,
Source = "source",
Target = "target",
Value = "value",
NodeID = "name",
units = "articles",
fontSize = 20,
nodeWidth = 30,
height = 800
)
The flow diagram (a.k.a. alluvial plot) shows relationships across clusters at different levels of resolution:
library(ggalluvial)
library(colorspace)
cluster_flow <- clustering_df %>%
group_by(Coarse, Intermediate, Fine) %>%
summarise(Freq = n()) %>%
filter(Freq >= 20)
colors_raw <- qualitative_hcl(26, palette = "Dynamic")
reordered_indices <- c(seq(1, 26, 2), seq(2, 26, 2))
colors_reordered <- colors_raw[reordered_indices]
cluster_ids <- sort(unique(cluster$Cluster_Label))
cluster_color_map <- setNames(colors_reordered, cluster_ids)
alluvial_plt <- ggplot(data = cluster_flow, aes(y = Freq,
axis1 = Coarse,
axis2 = Intermediate,
axis4 = Fine))+
geom_flow(aes(fill = after_stat(stratum)))+
geom_stratum(width = 0.5, aes(fill = after_stat(stratum)))+
geom_label(stat = "stratum", aes(label=after_stat(stratum)))+
scale_x_discrete(limits = c("Coarse", "Intermediate","Fine"))+
scale_fill_manual(values = cluster_color_map) +
theme_classic()+
theme(legend.position = "none")+
labs(x = "Resolution", y = "Number of articles",
title = "Clustering solutions at different resolutions",
subtitle = "Flows of 20 articles or less are not shown",
caption = "R package: ggalluvial")
ggsave("alluvial.png", dpi = 500, units = "cm", height = 40, width =40)
Most productive authors (production broken down by journal, only authors with at least 10 articles are considered):
actor_associations <- actor %>%
inner_join(association %>% mutate(ass_role = paste0(Association, " (", Role, ")")) %>% select(H_ID, ass_role), by = "H_ID") %>%
group_by(H_ID) %>%
summarise(all_ass_roles = paste(unique(ass_role), collapse = ", "))
actor_prizes <- actor %>%
inner_join(prize %>% mutate(prize_year = paste0(Prize, " (", Award_Year, ")")) %>% select(H_ID, prize_year), by = "H_ID") %>%
group_by(H_ID) %>%
summarise(prizes = paste(unique(prize_year), collapse = ", "))
authorship %>%
group_by(H_ID)%>%
summarise(n_articles = n_distinct(UT))%>%
filter(n_articles >= 10) %>%
inner_join(authorship, by = "H_ID") %>%
left_join(article %>% select(UT, Journal_Abb), by = "UT") %>%
group_by(H_ID, Journal_Abb) %>%
summarise(n_articles = n_distinct(UT)) %>%
left_join(actor, by = "H_ID") %>%
select(H_ID, Standard_Label, Gender, Journal_Abb, n_articles) %>%
pivot_wider(names_from = Journal_Abb, values_from = n_articles) %>%
mutate(across(where(is.numeric), ~ replace_na(., 0))) %>%
mutate(tot_articles = rowSums(across(where(is.numeric)))) %>%
arrange(desc(tot_articles)) %>%
left_join(actor_associations, by = "H_ID") %>%
left_join(actor_prizes, by = "H_ID") %>%
reactable(searchable = TRUE, filterable = TRUE, sortable = TRUE)
Most mentioned actors:
mention %>%
filter(H_ID %in% actor$H_ID) %>%
group_by(H_ID) %>%
summarise(n_mentions = n_distinct(UT)) %>%
left_join(actor, by = "H_ID") %>%
left_join(actor_associations, by = "H_ID") %>%
left_join(actor_prizes, by = "H_ID") %>%
select(H_ID, Standard_Label, all_ass_roles, prizes, Gender, n_mentions) %>%
arrange(desc(n_mentions)) %>%
reactable(searchable = TRUE,resizable = TRUE, filterable = TRUE)
Correlations between variables at the individual level:
library(corrplot)
men_con_ind <- mention %>%
filter(H_ID %in% actor$H_ID) %>%
group_by(H_ID) %>%
summarise(n_mentions = n_distinct(UT))
author_productivity <- authorship %>%
group_by(H_ID)%>%
summarise(n_articles = n_distinct(UT))
ind_data <- actor %>%
select(H_ID) %>%
left_join(men_con_ind, by = "H_ID") %>%
mutate(n_mentions = ifelse(is.na(n_mentions), 0, n_mentions)) %>%
left_join(author_productivity, by = "H_ID") %>%
mutate(n_articles = ifelse(is.na(n_articles), 0, n_articles))
cor_matrix <- cor(ind_data %>% select(n_articles, n_mentions), use = "pairwise.complete.obs", method = "pearson")
corrplot(cor_matrix, method = "color", type = "upper",
tl.col = "black", tl.srt = 45, addCoef.col = "white", diag = FALSE,
number.digits = 3)
cor_matrix <- cor(ind_data %>% select(n_articles, n_mentions) %>% filter(n_articles > 0 & n_mentions > 0), use = "pairwise.complete.obs", method = "pearson")
corrplot(cor_matrix, method = "color", type = "upper",
tl.col = "black", tl.srt = 45, addCoef.col = "white", diag = FALSE,
number.digits = 3)
Most prestigious institutions (by authorship, number of distinct authors, number of mentions, and number of officials):
officials_per_inst <- actor_associations %>%
left_join(affiliation %>% distinct(H_ID, Inst_ID), by = "H_ID") %>%
group_by(Inst_ID) %>%
summarise(n_officials = n_distinct(H_ID))
mention_inst <- mention %>%
filter(H_ID %in% actor$H_ID) %>%
left_join(article %>% select(UT, Pub_Year), by = "UT") %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
group_by(Inst_ID) %>%
summarise(n_mentions = n_distinct(UT)) %>%
left_join(institution, by = "Inst_ID") %>%
arrange(desc(n_mentions)) %>%
select(Inst_ID, n_mentions) %>%
filter(!is.na(Inst_ID))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 2 of `x` matches multiple rows in `y`.
## ℹ Row 16636 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
inst_data <- authorship %>%
left_join(article %>% select(UT, Pub_Year), by = "UT") %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
group_by(Inst_ID) %>%
summarise(n_authorships = n_distinct(AT_ID),
n_authors = n_distinct(H_ID)) %>%
left_join(officials_per_inst, by = "Inst_ID") %>%
full_join(mention_inst, by = "Inst_ID") %>%
left_join(institution, by = "Inst_ID")%>%
select(Inst_ID, Affiliation_Name, Affiliation_City, Affiliation_Country, n_mentions, n_authorships, n_authors, n_officials) %>%
filter(!is.na(Inst_ID)) %>%
mutate(across(where(is.numeric), ~ replace_na(., 0)))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 5 of `x` matches multiple rows in `y`.
## ℹ Row 18 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
inst_data %>%
arrange(desc(n_authorships)) %>%
reactable(searchable = TRUE,resizable = TRUE, filterable = TRUE, sortable = TRUE)
Correlations between variables at the institutional level:
vars <- inst_data %>%
select(n_mentions, n_authorships, n_authors, n_officials)
cor_matrix <- cor(vars, use = "pairwise.complete.obs", method = "pearson")
corrplot(cor_matrix, method = "color", type = "upper",
tl.col = "black", tl.srt = 45, addCoef.col = "white", diag = FALSE,
number.digits = 3)
Most prestigious countries (by authorship, number of distinct authors, number of mentions, and number of officials):
officials_per_country <- actor_associations %>%
left_join(affiliation %>% distinct(H_ID, Inst_ID), by = "H_ID") %>%
left_join(institution, by = "Inst_ID") %>%
group_by(Affiliation_Country) %>%
summarise(n_officials = n_distinct(H_ID))
country_mention <- mention %>%
filter(H_ID %in% actor$H_ID) %>%
left_join(article %>% select(UT, Pub_Year), by = "UT") %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
left_join(institution, by = "Inst_ID") %>%
group_by(Affiliation_Country) %>%
summarise(n_mentions = n_distinct(UT)) %>%
arrange(desc(n_mentions)) %>%
filter(!is.na(Affiliation_Country))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 2 of `x` matches multiple rows in `y`.
## ℹ Row 16636 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
country_data <- authorship %>%
left_join(article %>% select(UT, Pub_Year), by = "UT") %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
left_join(institution, by = "Inst_ID") %>%
group_by(Affiliation_Country) %>%
summarise(n_authorships = n_distinct(AT_ID),
n_authors = n_distinct(H_ID)) %>%
left_join(officials_per_country, by = "Affiliation_Country") %>%
full_join(country_mention, by = "Affiliation_Country") %>%
select(Affiliation_Country, n_mentions, n_authorships, n_authors, n_officials) %>%
filter(!is.na(Affiliation_Country)) %>%
mutate(across(where(is.numeric), ~ replace_na(., 0))) %>%
arrange(desc(n_authorships))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 5 of `x` matches multiple rows in `y`.
## ℹ Row 18 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
country_data %>%
reactable(searchable = TRUE,resizable = TRUE, filterable = TRUE, sortable = TRUE)
Correlations between variables at the country level:
vars <- country_data %>%
select(n_mentions, n_authorships, n_authors, n_officials)
cor_matrix <- cor(vars, use = "pairwise.complete.obs", method = "pearson")
corrplot(cor_matrix, method = "color", type = "upper",
tl.col = "black", tl.srt = 45, addCoef.col = "white", diag = FALSE,
number.digits = 3)
We investigate the concentration of mentions and authorships at the individual and institutional levels, using the Lorentz Curve and the Gini index.
Let’s start with mentions
library(ineq)
lc_ind <- Lc(men_con_ind$n_mentions)
gini_ind <- ineq(men_con_ind$n_mentions, type = "Gini")
df_ind <- data.frame(p = lc_ind$p, L = lc_ind$L, group = "Individuals")
men_con_inst <- mention %>%
filter(H_ID %in% actor$H_ID) %>%
left_join(article %>% select(UT, Pub_Year), by = "UT") %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
group_by(Inst_ID) %>%
summarise(n_mentions = n_distinct(UT))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 2 of `x` matches multiple rows in `y`.
## ℹ Row 16636 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
lc_inst <- Lc(men_con_inst$n_mentions)
gini_inst <- ineq(men_con_inst$n_mentions, type = "Gini")
df_inst <- data.frame(p = lc_inst$p, L = lc_inst$L, group = "Institutions")
df_lc <- bind_rows(df_ind, df_inst)
p1<-ggplot(df_lc, aes(x = p, y = L, color = group)) +
geom_line(linewidth = 1.2) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "gray50") +
labs(
title = "Mentions",
x = "Cumulative share of actors",
y = "Cumulative share of mentions",
color = "Actor type"
) +
scale_color_manual(values = c("Individuals" = "darkred", "Institutions" = "steelblue")) +
theme_minimal(base_size = 14) +
theme(plot.title = element_text(hjust = 0.5)) +
scale_y_continuous(labels = scales::percent_format(),
breaks = seq(0, 1, 0.1),
limits = c(0, 1)) +
scale_x_continuous(labels = scales::percent_format(),
breaks = seq(0, 1, 0.1),
limits = c(0, 1)) +
annotate("text", x = 0.1, y = 0.9,
label = paste0("Gini (Individuals) = ", round(gini_ind, 3)),
color = "darkred", hjust = 0) +
annotate("text", x = 0.1, y = 0.8,
label = paste0("Gini (Institutions) = ", round(gini_inst, 3)),
color = "steelblue", hjust = 0)
p1
We use a negative binomial regression to check how the number of mentions is affected by various characteristics of the actors.
For a negative binomial regression, the expected mention count \(\mu_{i}\) for person \(i\) is modeled as:
\[Y_{i} \sim \text{NegBin}(\mu_{i}, \theta)\]
where:
Using a log-link, the model is:
\[log(\mu_{i}) = \beta_0 + \beta_1 \text{Awards}_{i} + \beta_2 \text{PhilSciPubs} + \beta_3 \text{GovRole} + \beta_4 \text{Eng} + \beta_5 \text{Gender} + \beta_6 \text{Pubs} + \beta_7 \text{Cits} + \beta_8\text{Funding}\]
where:
Let’s collect data from the various tables of SOPHIS.
NB: We remove all actors for which some data are missing. Of the \(\approx 10,000\) actors, \(\approx 40\%\) have no affiliation data recorded. This leaves us with \(6.477\) observations of \(8\) variables (\(1\) response variable + \(8\) predictors).
n_mentions = mention %>%
filter(!is.na(H_ID)) %>%
group_by(H_ID) %>%
summarise(n_mentions = n_distinct(UT))
n_awards <- prize %>%
group_by(H_ID) %>%
summarise(n_awards = n())
n_phil_sci_pubs <- authorship %>%
group_by(H_ID) %>%
summarise(n_phil_sci_pubs = n_distinct(UT))
n_gov_roles <- association %>%
group_by(H_ID) %>%
summarise(n_gov_roles = n_distinct(Assoc_ID))
english_countries <- c("United States", "United Kingdom", "Australia", "New Zealand")
english_speaking_authors <- authorship %>%
left_join(article, by = "UT") %>%
dplyr::select(H_ID, UT, Pub_Year) %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
left_join(institution, by = "Inst_ID") %>%
mutate(english_dummy = ifelse(is.na(Affiliation_Country), NA,
ifelse(Affiliation_Country %in% english_countries, 1,
0))) %>%
group_by(H_ID) %>%
summarise(english_aff = max(english_dummy))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 5 of `x` matches multiple rows in `y`.
## ℹ Row 18 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
english_speaking_ackgees <- mention %>%
left_join(article, by = "UT") %>%
inner_join(actor, by = "H_ID") %>%
dplyr::select(H_ID, UT, Pub_Year) %>%
left_join(affiliation, by = c("H_ID", "Pub_Year" = "Year")) %>%
left_join(institution, by = "Inst_ID") %>%
mutate(english_dummy = ifelse(is.na(Affiliation_Country), NA,
ifelse(Affiliation_Country %in% english_countries, 1,
0))) %>%
group_by(H_ID) %>%
summarise(english_aff = max(english_dummy))
## Warning in left_join(., affiliation, by = c("H_ID", Pub_Year = "Year")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 2 of `x` matches multiple rows in `y`.
## ℹ Row 16636 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
english_speaking_actors <- dplyr::union(english_speaking_authors, english_speaking_ackgees) %>%
group_by(H_ID) %>%
summarise(english_affiliation = max(english_aff))
gender <- actor %>%
mutate(gender_dummy = ifelse(Gender == "", NA,
ifelse(Gender == "male", 1, 0))) %>%
dplyr::select(H_ID, gender_dummy)
n_funded_articles <- authorship %>%
inner_join(funding %>% distinct(UT), by = "UT") %>%
group_by(H_ID) %>%
summarise(n_funded_articles = n_distinct(UT))
reg_data <- actor %>%
left_join(n_mentions, by = "H_ID") %>%
mutate(n_mentions = ifelse(is.na(n_mentions), 0, n_mentions)) %>%
mutate(gender_dummy = ifelse(Gender == "", NA,
ifelse(Gender == "male", 1, 0))) %>%
left_join(n_awards, by = "H_ID") %>%
mutate(n_awards = ifelse(is.na(n_awards), 0 , n_awards)) %>%
left_join(n_gov_roles, by = "H_ID") %>%
mutate(n_gov_roles = ifelse(is.na(n_gov_roles), 0, n_gov_roles)) %>%
left_join(english_speaking_actors, by = "H_ID") %>%
left_join(n_phil_sci_pubs, by = "H_ID") %>%
mutate(n_phil_sci_pubs = ifelse(is.na(n_phil_sci_pubs), 0 , n_phil_sci_pubs)) %>%
left_join(n_funded_articles, by = "H_ID") %>%
mutate(n_funded_articles = ifelse(is.na(n_funded_articles), 0, n_funded_articles)) %>%
dplyr::select(H_ID, n_mentions, n_awards, n_phil_sci_pubs, n_gov_roles, english_affiliation, gender_dummy, Tot_Publications, Citations, n_funded_articles) %>%
#### WE REMOVE ACTORS WITH MISSING DATA
filter(!is.na(english_affiliation)) %>%
filter(!is.na(gender_dummy)) %>%
filter(!is.na(Tot_Publications)) %>%
filter(!is.na(Citations))
reg_data %>%
left_join(actor %>% dplyr::select(H_ID, Standard_Label), by = "H_ID") %>%
dplyr::select(Standard_Label, n_mentions, n_awards, n_phil_sci_pubs, n_gov_roles, english_affiliation, gender_dummy, Tot_Publications, Citations, n_funded_articles) %>%
arrange(desc(n_mentions)) %>%
reactable(searchable = TRUE, filterable = TRUE, sortable = TRUE)
summary(reg_data)
## H_ID n_mentions n_awards n_phil_sci_pubs
## Length:6477 Min. : 0.00 Min. :0.0000 Min. : 0.000
## Class :character 1st Qu.: 0.00 1st Qu.:0.0000 1st Qu.: 0.000
## Mode :character Median : 1.00 Median :0.0000 Median : 1.000
## Mean : 2.49 Mean :0.0088 Mean : 1.073
## 3rd Qu.: 2.00 3rd Qu.:0.0000 3rd Qu.: 1.000
## Max. :104.00 Max. :2.0000 Max. :22.000
## n_gov_roles english_affiliation gender_dummy Tot_Publications
## Min. : 0.00000 Min. :0.0000 Min. :0.0000 Min. : 1.00
## 1st Qu.: 0.00000 1st Qu.:0.0000 1st Qu.:1.0000 1st Qu.: 10.00
## Median : 0.00000 Median :1.0000 Median :1.0000 Median : 21.00
## Mean : 0.05558 Mean :0.5864 Mean :0.7774 Mean : 39.07
## 3rd Qu.: 0.00000 3rd Qu.:1.0000 3rd Qu.:1.0000 3rd Qu.: 43.00
## Max. :14.00000 Max. :1.0000 Max. :1.0000 Max. :1781.00
## Citations n_funded_articles
## Min. : 0 Min. : 0.0000
## 1st Qu.: 48 1st Qu.: 0.0000
## Median : 186 Median : 0.0000
## Mean : 1347 Mean : 0.3585
## 3rd Qu.: 650 3rd Qu.: 0.0000
## Max. :228493 Max. :12.0000
Viz. of the mention distribution
Let’s compute the overdispersion of mentions to check if a Negative Binomial is the most appropriate model
overdispersion <- var(reg_data$n_mentions) / mean(reg_data$n_mentions)
print(overdispersion)
## [1] 11.60813
Since \(\text{Var}(Y) >> \text{Mean}(Y)\), the Negative Binomial is the correct choice.
Now let’s estimate the parameters.
library(MASS)
m_nb <- glm.nb(n_mentions ~ n_awards + n_phil_sci_pubs + n_gov_roles + gender_dummy + english_affiliation + Tot_Publications + Citations + n_funded_articles,
data = reg_data)
summary(m_nb)
##
## Call:
## glm.nb(formula = n_mentions ~ n_awards + n_phil_sci_pubs + n_gov_roles +
## gender_dummy + english_affiliation + Tot_Publications + Citations +
## n_funded_articles, data = reg_data, init.theta = 1.138175011,
## link = log)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -2.213e-01 3.995e-02 -5.540 3.02e-08 ***
## n_awards 8.899e-01 1.242e-01 7.164 7.85e-13 ***
## n_phil_sci_pubs 2.490e-01 1.063e-02 23.420 < 2e-16 ***
## n_gov_roles 2.098e-01 2.696e-02 7.781 7.19e-15 ***
## gender_dummy 1.596e-01 3.696e-02 4.316 1.59e-05 ***
## english_affiliation 6.494e-01 3.147e-02 20.636 < 2e-16 ***
## Tot_Publications 2.331e-03 3.357e-04 6.943 3.83e-12 ***
## Citations -2.091e-05 4.081e-06 -5.123 3.01e-07 ***
## n_funded_articles -3.125e-03 2.001e-02 -0.156 0.876
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for Negative Binomial(1.1382) family taken to be 1)
##
## Null deviance: 9491.1 on 6476 degrees of freedom
## Residual deviance: 6528.0 on 6468 degrees of freedom
## AIC: 24388
##
## Number of Fisher Scoring iterations: 1
##
##
## Theta: 1.1382
## Std. Err.: 0.0323
##
## 2 x log-likelihood: -24368.3140
Let’s tidy a bit the results and exponentiate the estimates in order to interpret them as percentage change:
library(broom)
broom::tidy(m_nb) %>%
mutate(
exp_estimate = exp(estimate), # multiplicative effect
pct_change = (exp(estimate) - 1) * 100 # percent change
) %>%
dplyr::select(term, estimate, std.error, p.value, exp_estimate, pct_change) %>%
mutate(across(c(estimate, std.error, exp_estimate, pct_change), ~round(., 3)),
p.value = signif(p.value, 2)) %>%
# arrange(desc(abs(pct_change))) %>%
reactable()
Since gender is an important variable that affects the number of mentions received significantly, we focus on gender distribution in the populations of authors and acknowledgees:
actor %>%
mutate(is_author = ifelse(H_ID %in% authorship$H_ID, 1, 0),
is_ackgee = ifelse(H_ID %in% mention$H_ID, 1, 0)) %>%
group_by(Gender) %>%
summarise(n_authors = sum(is_author),
n_ackgees = sum(is_ackgee),
n_actors = n_distinct(H_ID)) %>%
mutate(perc_authors = round(n_authors/sum(n_authors)*100,1),
perc_ackgees = round(n_ackgees/sum(n_ackgees)*100,1),
perc_actors = round(n_actors/sum(n_actors)*100,1)) %>%
dplyr::select(Gender,
n_actors, perc_actors,
n_authors, perc_authors,
n_ackgees, perc_ackgees) %>%
reactable()
actor %>%
mutate(is_author = ifelse(H_ID %in% authorship$H_ID, 1, 0),
is_ackgee = ifelse(H_ID %in% mention$H_ID, 1, 0),
Gender = ifelse(Gender == "", "unknown", Gender)) %>%
group_by(Gender) %>%
summarise(n_authors = sum(is_author),
n_ackgees = sum(is_ackgee)) %>%
pivot_longer(cols = -Gender, names_to = "Population", values_to = "Frequency") %>%
ggplot(aes(x = Population, y = Frequency, fill = Gender)) +
geom_col(position = "fill") +
scale_y_continuous(labels = scales::percent_format()) +
labs(y = "Proportion")+
theme_classic()
Social map of philosophy of science
Co-authorship is rare in philosophy of science.
Average number of authors per paper and distribution of papers by number of co-authors:
Average number of co-authors per author:
Co-acknowledgement map
Open interactive VOSviewer map
Each node represents an ackwnoweldgee mentioned at least 10 times in SOPHIS’ acknoweldgments collection (\(n = 447\), top \(5\%\) of all acknowledgees).
The distance between nodes represent the co-acknowledgment similarity: philosophers that are frequently mentioned together in the same acknowledgments are placed closer on the map, while philosophers that are seldom mentioned together are placed far apart.
The size of the node represents the number of mentions received.
The color of the node represents the cluster the node belongs to. Cluster labels are attributed based on the specialization of the acknowledgees belonging to the cluster.
If you hover over a node, a text box will appear with the following information:
NB: To recreate the map in the paper, change the resolution parameter to \(0.9\) and rotate the map