Being Picky About Free Textbooks From Springer
You might have heard that Springer Nature, an American German academic publishing company, is giving free access to more than 500 key textbooks across Springer Nature’s eBook subject collections, as their way to support lecturers, teachers and students and grant remote access to essential educational resources during this Covid-19 lockdown period. A repository of the books can be found here.
To my delight, I stumbled across the springerQuarantineBooksR package made by Renan Xavier Cortes in a blog post. After a preliminary scan of the package, I thought this would make for a good opportunity to practise some exploratory data analysis to explore ways to narrow down a search across hundreds of books and their descriptions.
#devtools::install_github("renanxcortes/springerQuarantineBooksR")
library(springerQuarantineBooksR)
library(tidyverse)
theme_set(theme_minimal())
Once the package is installed, you are now able to download the entire batch of books all at once with this line of code.
#setwd('path_of_your_choice')
#download_springer_book_files()
Diving a bit deeper into the package has to offer, I noticed the dataset consists of only 407 rows and 391 unique book titles available for download - not quite the >500 aforementioned textbooks that was cited in the article linked above.
library(skimr)
springer_table <- download_springer_table()
skim(springer_table)
| Name | springer_table |
| Number of rows | 407 |
| Number of columns | 22 |
| _______________________ | |
| Column type frequency: | |
| character | 20 |
| logical | 1 |
| numeric | 1 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| book_title | 0 | 1.00 | 7 | 89 | 0 | 391 | 0 |
| author | 0 | 1.00 | 8 | 115 | 0 | 380 | 0 |
| edition | 0 | 1.00 | 4 | 13 | 0 | 79 | 0 |
| product_type | 0 | 1.00 | 9 | 40 | 0 | 4 | 0 |
| copyright_holder | 0 | 1.00 | 12 | 131 | 0 | 43 | 0 |
| print_isbn | 0 | 1.00 | 17 | 17 | 0 | 407 | 0 |
| electronic_isbn | 0 | 1.00 | 17 | 17 | 0 | 407 | 0 |
| language | 0 | 1.00 | 2 | 2 | 0 | 1 | 0 |
| language_collection | 0 | 1.00 | 21 | 21 | 0 | 1 | 0 |
| e_book_package | 0 | 1.00 | 5 | 5 | 0 | 21 | 0 |
| english_package_name | 0 | 1.00 | 6 | 38 | 0 | 21 | 0 |
| series_print_issn | 217 | 0.47 | 9 | 9 | 0 | 57 | 0 |
| series_electronic_issn | 219 | 0.46 | 9 | 9 | 0 | 55 | 0 |
| series_title | 209 | 0.49 | 6 | 64 | 0 | 64 | 0 |
| volume_number | 379 | 0.07 | 1 | 3 | 0 | 27 | 0 |
| doi_url | 0 | 1.00 | 29 | 40 | 0 | 407 | 0 |
| open_url | 0 | 1.00 | 66 | 66 | 0 | 407 | 0 |
| subject_classification | 0 | 1.00 | 11 | 337 | 0 | 392 | 0 |
| publisher | 0 | 1.00 | 11 | 33 | 0 | 12 | 0 |
| imprint | 0 | 1.00 | 8 | 18 | 0 | 4 | 0 |
Variable type: logical
| skim_variable | n_missing | complete_rate | mean | count |
|---|---|---|---|---|
| german_package_name | 407 | 0 | NaN | : |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| copyright_year | 0 | 1 | 2014.64 | 4.48 | 1993 | 2013 | 2016 | 2018 | 2020 | ▁▁▁▃▇ |
I looked around at the Github repo and found this explanation by the author:
“Currently, due to the fact that the package works with the book title (in order to facilitate the creation of subgroups of books by title), It fetches the latest edition of each book. In fact, that’s why the number of books generated is lower than the number of lines of the .xlsx file catalog that springer made available. Perhaps, it is worth to check an alternative to download all editions of the same book.”
Perusing the column names together with a head()+transmute() combo, I’ll filter out some columns which doesn’t interest me.
colnames(springer_table)
## [1] "book_title" "author" "edition"
## [4] "product_type" "copyright_year" "copyright_holder"
## [7] "print_isbn" "electronic_isbn" "language"
## [10] "language_collection" "e_book_package" "english_package_name"
## [13] "german_package_name" "series_print_issn" "series_electronic_issn"
## [16] "series_title" "volume_number" "doi_url"
## [19] "open_url" "subject_classification" "publisher"
## [22] "imprint"
t(head(springer_table, 3))
## [,1]
## book_title "Fundamentals of Power Electronics"
## author "Robert W. Erickson, Dragan Maksimovic"
## edition "2nd ed. 2001"
## product_type "Graduate/advanced undergraduate textbook"
## copyright_year "2001"
## copyright_holder "Springer Science+Business Media New York"
## print_isbn "978-0-7923-7270-7"
## electronic_isbn "978-0-306-48048-5"
## language "EN"
## language_collection "English/International"
## e_book_package "11647"
## english_package_name "Engineering"
## german_package_name NA
## series_print_issn NA
## series_electronic_issn NA
## series_title NA
## volume_number NA
## doi_url "http://doi.org/10.1007/b100747"
## open_url "http://link.springer.com/openurl?genre=book&isbn=978-0-306-48048-5"
## subject_classification "Engineering; Circuits and Systems; Energy, general; Electronics and Microelectronics, Instrumentation; Energy Systems"
## publisher "Springer US"
## imprint "Springer"
## [,2]
## book_title "Handbook of the Life Course"
## author "Jeylan T. Mortimer, Michael J. Shanahan"
## edition "2003"
## product_type "Graduate/advanced undergraduate textbook"
## copyright_year "2003"
## copyright_holder "Springer Science+Business Media New York"
## print_isbn "978-0-306-47498-9"
## electronic_isbn "978-0-306-48247-2"
## language "EN"
## language_collection "English/International"
## e_book_package "11648"
## english_package_name "Humanities, Social Sciences and Law"
## german_package_name NA
## series_print_issn "1389-6903"
## series_electronic_issn "2542-839X"
## series_title "Handbooks of Sociology and Social Research"
## volume_number NA
## doi_url "http://doi.org/10.1007/b100507"
## open_url "http://link.springer.com/openurl?genre=book&isbn=978-0-306-48247-2"
## subject_classification "Social Sciences; Sociology, general; Clinical Psychology; Population Economics"
## publisher "Springer US"
## imprint "Springer"
## [,3]
## book_title "All of Statistics"
## author "Larry Wasserman"
## edition "2004"
## product_type "Graduate/advanced undergraduate textbook"
## copyright_year "2004"
## copyright_holder "Springer-Verlag New York"
## print_isbn "978-0-387-40272-7"
## electronic_isbn "978-0-387-21736-9"
## language "EN"
## language_collection "English/International"
## e_book_package "11649"
## english_package_name "Mathematics and Statistics"
## german_package_name NA
## series_print_issn "1431-875X"
## series_electronic_issn "2197-4136"
## series_title "Springer Texts in Statistics"
## volume_number NA
## doi_url "http://doi.org/10.1007/978-0-387-21736-9"
## open_url "http://link.springer.com/openurl?genre=book&isbn=978-0-387-21736-9"
## subject_classification "Mathematics; Computational Mathematics and Numerical Analysis; Probability Theory and Stochastic Processes; Complex Systems; Statistical Theory and Methods; Probability and Statistics in Computer Science; Statistics for Engineering, Physics, Computer Science, Chemistry and Earth Sciences"
## publisher "Springer New York"
## imprint "Springer"
springer <- springer_table %>%
select(1:3, 9, 12, 16:17, 19:21) #selecting columns that I want
I want to explore two columns specifically:
english_package_name: General categorisation e.g. Mathematics and Statistics, Engineering, Computer Science, etc.subject_classification: Specific sub-categories e.g. Linear and Multilinear Algebras, Chemical Engineering, Python, etc.
springer %>%
count(english_package_name, sort = T)
## # A tibble: 21 x 2
## english_package_name n
## <chr> <int>
## 1 Mathematics and Statistics 56
## 2 Computer Science 49
## 3 Physics and Astronomy 43
## 4 Engineering 36
## 5 Chemistry and Materials Science 33
## 6 Biomedical and Life Sciences 32
## 7 Business and Management 25
## 8 Business and Economics 16
## 9 Behavioral Science 11
## 10 Behavioral Science and Psychology 10
## # ... with 11 more rows
springer %>%
count(subject_classification, sort = T)
## # A tibble: 392 x 2
## subject_classification n
## <chr> <int>
## 1 Engineering; Circuits and Systems; Processor Architectures; Logic Desi~ 6
## 2 Mathematics; Analysis 4
## 3 Chemistry; Food Science; Industrial Chemistry/Chemical Engineering; Sp~ 2
## 4 Computer Science; Computer Appl. in Administrative Data Processing; Bu~ 2
## 5 Computer Science; Programming Languages, Compilers, Interpreters; Pyth~ 2
## 6 Engineering; Control; Systems Theory, Control; Computer Applications 2
## 7 Mathematics; Linear and Multilinear Algebras, Matrix Theory 2
## 8 Physics; Numerical and Computational Physics, Simulation; Mathematical~ 2
## 9 Social Sciences; Archaeology 2
## 10 Biomedicine; Biomedical Engineering/Biotechnology; Biomedical Engineer~ 1
## # ... with 382 more rows
So if one is looking for a book, it’s better to search for sub-categories under subject_classification. However, multiple sub-categories are listed under subject_classification column per book. I plan to separate() then pivot_longer() to make our data tidy.
springer2 <- springer %>%
separate(subject_classification, paste("category", 1:8, sep = ""), sep = ";")
springer2 %>%
select(book_title, english_package_name, contains("cat"))
## # A tibble: 407 x 10
## book_title english_package~ category1 category2 category3 category4 category5
## <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 Fundament~ Engineering Engineer~ " Circui~ " Energy~ " Electr~ " Energy~
## 2 Handbook ~ Humanities, Soc~ Social S~ " Sociol~ " Clinic~ " Popula~ <NA>
## 3 All of St~ Mathematics and~ Mathemat~ " Comput~ " Probab~ " Comple~ " Statis~
## 4 Social An~ Behavioral Scie~ Psycholo~ " Clinic~ " Person~ " Commun~ <NA>
## 5 Discrete ~ Mathematics and~ Mathemat~ " Combin~ " Number~ <NA> <NA>
## 6 Developme~ Biomedical and ~ Biomedic~ " Neuros~ " Anatom~ " Neurob~ <NA>
## 7 Intuitive~ Engineering Engineer~ " Commun~ " Mathem~ " Mathem~ " Signal~
## 8 Handbook ~ Humanities, Soc~ Social S~ " Sociol~ " Public~ " Demogr~ <NA>
## 9 Handbook ~ Humanities, Soc~ Social S~ " Sociol~ " Social~ " Person~ <NA>
## 10 Handbook ~ Humanities, Soc~ Social S~ " Sociol~ " Politi~ <NA> <NA>
## # ... with 397 more rows, and 3 more variables: category6 <chr>,
## # category7 <chr>, category8 <chr>
This part of the data wrangling required some trial and error to gauge how many columns I needed to completely seperate all sub-categories. From another round of skim, it looks like only 7 columns were needed because column category8 has all 407 rows with missing values i.e. all sub-categories have been separated.
skim(springer2)
| Name | springer2 |
| Number of rows | 407 |
| Number of columns | 17 |
| _______________________ | |
| Column type frequency: | |
| character | 17 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| book_title | 0 | 1.00 | 7 | 89 | 0 | 391 | 0 |
| author | 0 | 1.00 | 8 | 115 | 0 | 380 | 0 |
| edition | 0 | 1.00 | 4 | 13 | 0 | 79 | 0 |
| language | 0 | 1.00 | 2 | 2 | 0 | 1 | 0 |
| english_package_name | 0 | 1.00 | 6 | 38 | 0 | 21 | 0 |
| series_title | 209 | 0.49 | 6 | 64 | 0 | 64 | 0 |
| volume_number | 379 | 0.07 | 1 | 3 | 0 | 27 | 0 |
| open_url | 0 | 1.00 | 66 | 66 | 0 | 407 | 0 |
| category1 | 0 | 1.00 | 3 | 32 | 0 | 25 | 0 |
| category2 | 2 | 1.00 | 5 | 65 | 0 | 209 | 405 |
| category3 | 32 | 0.92 | 5 | 88 | 0 | 241 | 375 |
| category4 | 86 | 0.79 | 8 | 85 | 0 | 219 | 321 |
| category5 | 201 | 0.51 | 6 | 88 | 0 | 161 | 206 |
| category6 | 306 | 0.25 | 9 | 56 | 0 | 93 | 101 |
| category7 | 367 | 0.10 | 11 | 84 | 0 | 39 | 40 |
| category8 | 407 | 0.00 | NA | NA | 0 | 0 | 0 |
| publisher | 0 | 1.00 | 11 | 33 | 0 | 12 | 0 |
Another way to scan for missing values would be using the map_df() function by the purrr package. Once again we obtain the same outcome.
springer2 %>%
select(contains("cat")) %>%
map_df(~sum(is.na(.)))
## # A tibble: 1 x 8
## category1 category2 category3 category4 category5 category6 category7
## <int> <int> <int> <int> <int> <int> <int>
## 1 0 2 32 86 201 306 367
## # ... with 1 more variable: category8 <int>
I’ll proceed to remove that column, followed by making our data long with pivot_longer() so that we can process it later.
springer_tidy <- springer2 %>%
select(-category8) %>%
pivot_longer(
9:15,
names_to = "cat",
values_to = "categories",
values_drop_na = T
)
springer_tidy %>%
select(book_title, english_package_name, categories) %>%
add_count(categories) %>%
filter(n > 10) %>%
ggplot(aes(fct_reorder(categories, n), n/n, fill = fct_lump_n(english_package_name, 10))) +
geom_col() +
coord_flip() +
labs(
title = "Exploring the springerQuarantineBooksR package",
subtitle = "The free e-books feature a good mix across all disciplines",
fill = "Broad Categories",
x = "Sub-categories",
y = "Number of Times Mentioned"
)

Some observations:
- Physics, Mathematics, Chemistry and Statistics are very straightforward categories - you won’t be surprised
- Categories like Mathematical & Computer Engineering and Statistics & Computing, on the other hand, are inter-disciplinary.
To narrow down potentially interesting sub-categories, I created word clouds to highlight the most popular ones, in the event anyone is looking for inspiration on topics to dive into.
library(ggwordcloud)
wordcloud <- springer_tidy %>%
add_count(categories) %>%
mutate(english_package_name = fct_lump_n(english_package_name, 5)) %>%
filter(n < 15) %>%
group_by(english_package_name) %>%
sample_n(40) %>%
distinct(categories, n, english_package_name)
ggplot(wordcloud,
aes(label = categories,
size = n,
colour = english_package_name
)) +
geom_text_wordcloud_area(
rm_outside = T,
area_corr_power = 0.7,
eccentricity = 1,
shape = "pentagon"
) +
scale_size_area(max_size = 8) +
facet_wrap(. ~ english_package_name, nrow = 3) +
labs(title = "Analysis paralysis setting in with hundred of books to choose from?",
subtitle = "Word clouds are a way to visualize text data; Size of the word is an indication of its popularity in each category") +
theme(
plot.title = element_text(face = "bold", size = 25, hjust = 0.5),
plot.subtitle = element_text(size = 15, hjust = 0.5),
strip.background = element_blank(),
strip.text = element_text(face = "bold", size = 20)
)

And just because my wife was curious what the most “obsure” sub-categories were …
wordcloud2 <- springer_tidy %>%
add_count(categories) %>%
mutate(english_package_name = fct_lump_n(english_package_name, 5)) %>%
filter(n < 4) %>%
group_by(english_package_name) %>%
sample_n(30) %>%
distinct(categories, n, english_package_name)
ggplot(wordcloud2,
aes(label = categories,
size = n,
colour = english_package_name
)) +
geom_text_wordcloud_area(
rm_outside = T,
area_corr_power = 0.7,
eccentricity = 1,
shape = "pentagon"
) +
scale_size_area(max_size = 8) +
facet_wrap(. ~ english_package_name, nrow = 3) +
labs(title = "What are some potentially esoteric sub-categories?",
subtitle = "These are a sample of sub-categories that are cited three times or less.") +
theme(
plot.title = element_text(face = "bold", size = 25, hjust = 0.5),
plot.subtitle = element_text(size = 15, hjust = 0.5),
strip.background = element_blank(),
strip.text = element_text(face = "bold", size = 20)
)

Once you’ve decided on a category, say Sociology, the following code brings up the book title, author, and the relevant download link
springer_tidy %>%
mutate(open_url = paste0('<a target="_blank" href="',
open_url,
'">SpringerLink</a>')) %>%
filter(str_detect(categories, "Sociology")) %>%
distinct(book_title, english_package_name, open_url) %>%
knitr::kable() %>%
kableExtra::kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
| book_title | english_package_name | open_url |
|---|---|---|
| Handbook of the Life Course | Humanities, Social Sciences and Law | SpringerLink |
| Handbook of Disaster Research | Humanities, Social Sciences and Law | SpringerLink |
| Handbook of the Sociology of Gender | Humanities, Social Sciences and Law | SpringerLink |
| Handbook of Sociological Theory | Humanities, Social Sciences and Law | SpringerLink |
| Grammar for Teachers | Education | SpringerLink |
| Business Process Management Cases | Business and Management | SpringerLink |
| Teaching Medicine and Medical Ethics Using Popular Culture | Literature, Cultural and Media Studies | SpringerLink |
| Business Ethics - A Philosophical and Behavioral Approach | Religion and Philosophy | SpringerLink |
I wanted to end the blog post with a showcase of the DT package that Antoine Soetewey highlights in his blog post. Unfortunately, after spending some time experimenting with StackOverflow solutions, I’m still not able to resolve the technical issues preventing me from displaying html widgets on this particular Hugo theme.