Skip to contents

The following vignette will demonstrate the basic functionality (i.e., awesome might) of c2z.

Collections

Your computer desktop reveals your D&D alignment. Similarly, the way you structure your Zotero library through collections reveals your personality. (Chaotic neutral is not using collections, obviously.)

c2z lets you create nested collections on a whim, as seen below.

Creating collections

In this example we define a vector of nine elements based on “the quick brown fox jumps over the lazy dog”. We use the Zotero function, a wrapper that connects with the Zotero API and combines all major functions in c2z, to create nine nested collections based on the string (i.e., “dog” nested in “lazy” nested in “the” et cetera). We set library = TRUE, thereby querying the Zotero library, and create = TRUE to create any collections that does not exist (i.e., all nine collections).

This is a recursive action, so let’s keep the noise at a minimum and set silent = TRUE.

# Let's create a vector of nested collections
collection.names <- c(
  "The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"
  )

# Create the collections
create.collections <- Zotero(
  collection.names = collection.names,
  library = TRUE,
  create = TRUE,
  silent = TRUE
)

Using collections

Now that we have created some collections, we can access the collections in different ways. By default, the function will find the last element in the vector (i.e., “dog”).

# Dog
create.collections$collections  |>
  dplyr::select(c(key, name, parentCollection))
#> # A tibble: 1 × 3
#>   key      name  parentCollection
#>   <chr>    <chr> <chr>           
#> 1 579FP7HH dog   7ZAXF9MY

However, we often want access all, or some, nested collections. We can achieve this using two different approaches. 1) using recursive = TRUE to access the given element and then recursively find the nested collections, or 2) using ancestor = TRUE and trace the lineage of the specified collection.

You may use collection.name (you don’t have several top-level collections with the same name, do you? If so, the function will select the newest collection with the specified name), or collection.key if you keep track of such things. c2z will by default look for collections names regardless of letter case, and if this is important set case.insensitive = FALSE.

All collections

# Find collections
collections <- Zotero(
  collection.names = "The",
  recursive = TRUE,
  library = TRUE,
  silent = TRUE
)

# print collections
collections$collections |>
  dplyr::select(c(key, name, parentCollection))
#> # A tibble: 9 × 3
#>   key      name  parentCollection
#>   <chr>    <chr> <chr>           
#> 1 CXS788XY the   48EL4CUF        
#> 2 M75G3P8M jumps TY4WJJ95        
#> 3 579FP7HH dog   7ZAXF9MY        
#> 4 7ZAXF9MY lazy  CXS788XY        
#> 5 48EL4CUF over  M75G3P8M        
#> 6 TY4WJJ95 fox   R54JTKTX        
#> 7 R54JTKTX brown ZAMUWCTB        
#> 8 ZAMUWCTB quick SHSJ6MR8        
#> 9 SHSJ6MR8 The   FALSE

Recursive path

# Find collections
recursive <- Zotero(
  collection.names = c("The", "quick", "brown"),
  recursive = TRUE,
  library = TRUE,
  silent = TRUE
)

# print collections
recursive$collections |>
  dplyr::select(name)
#> # A tibble: 7 × 1
#>   name 
#>   <chr>
#> 1 the  
#> 2 jumps
#> 3 dog  
#> 4 lazy 
#> 5 over 
#> 6 fox  
#> 7 brown

Ancestor path

# Find collections
ancestor <- Zotero(
  collection.names = c("The", "quick", "brown", "fox"),
  ancestor = TRUE,
  library = TRUE,
  silent = TRUE
)

# print collections
ancestor$collections |>
  dplyr::select(name)
#> # A tibble: 4 × 1
#>   name 
#>   <chr>
#> 1 fox  
#> 2 brown
#> 3 quick
#> 4 The

Items

Okay, so we’ve created a bunch of collections. Kinda cool, I guess? But collections are somewhat pointless without items. Sooo, lets add some items!

Adding items from Cristin

One motivation to develop c2z is an attempt at making registering data at Cristin feel useful. The following example of the Cristin function will gather all publications containing the word “cheese”, published since 2020 (it was a good year for cheese, probably).

The zotero.import is set to TRUE, indicating that the function will use CristinWrangler to convert Cristin metadata into an acceptable format for Zotero. use.identifiers = TRUE and the function will therefore use any identifiers (i.e., ISBN or DOI) to augment the metadata. Furthermore, crossref.search = TRUE, meaning that the function will search CrossRef for metadata if identifiers are missing. Also, autosearch = TRUE and the process is consequently conducted automatically. The items are posted to the Zotero library using Zotero, where items are uploaded using the items argument. The index = TRUE, creating an index of the items.

Please note that crossref.search should normally not be used (see the README).

Rather than finicking over h-indexes and impact factors, Norwegians have a weird obsession with NVI (Norwegian Science Index), which is a two-level index where level one publications are ordinary and level two are great (it probably makes sense). However, Cristin has no simple method of filtering out publications that qualify for the two levels.

The specified filter arguments try to filter out categories that usually contain publications that are worthy of the NVI (see all the Cristin categories).

Let’s post the results to the collection “fox”, while using the argument get.items = FALSE as not to add any items in the collection to the Zotero list.

# Filter items
cristin.filter <- c(
  "ACADEMICREVIEW",
  "ARTICLEJOURNAL",
  "ARTICLE", 
  "ANTHOLOGYACA", 
  "CHAPTER", 
  "CHAPTERACADEMIC",
  "CHAPTERARTICLE",
  "COMMENTARYACA",
  "MONOGRAPHACA"
)

# Query Cristin
cristin <- Cristin(
  title = "cheese",
  published_since = 2020,
  published_before = 2021,
  filter = cristin.filter,
  zotero.import = TRUE,
  use.identifiers = TRUE,
  crossref.search = TRUE,
  autosearch = TRUE,
  silent = FALSE
)
#> Found 15 results 
#> Checking whether references are supported. See `CristinSupported()` 
#> Looking for missing data 
#> Filtered out 9 results 
#> Converting 6 references from Cristin to Zotero format 
#> 
——————Next ID: 1879488. Progress: 16.67% (1/6). ETA: 17.02.2024 - 21:31:53——————
——————Next ID: 1919520. Progress: 33.33% (2/6). ETA: 17.02.2024 - 21:31:43——————
——————Next ID: 1912219. Progress: 50.00% (3/6). ETA: 17.02.2024 - 21:31:39——————
——————Next ID: 1961743. Progress: 66.67% (4/6). ETA: 17.02.2024 - 21:31:37——————
——————Next ID: 1920677. Progress: 83.33% (5/6). ETA: 17.02.2024 - 21:31:35——————
—————————————————Process: 100.00% (6/6). Elapsed time: 00:00:15—————————————————

# Get the fox key
fox <- collections$collections |> 
  dplyr::filter(name == "fox") |> 
  dplyr::pull("key")

# Post the items to the collection called fox
post.cristin <- Zotero(
  collection.key = fox,
  metadata = cristin$results,
  library = TRUE,
  index = TRUE,
  post = TRUE,
  post.collections = FALSE,
  post.items = TRUE,
  silent = TRUE
)

# Select only names in index and print
post.cristin$index |> 
  dplyr::select(name) |>
  print(width = 80)
#> 
[38;5;246m# A tibble: 6 × 1
[39m
#>   name                                                                          
#>   
[3m
[38;5;246m<chr>
[39m
[23m                                                                         
#> 
[38;5;250m1
[39m Lundberg et al. (2021) Determination of maintenance Jarlsberg® cheese dose to…
#> 
[38;5;250m2
[39m Olsen et al. (2021) Feeding concentrates with different protein sources to hi…
#> 
[38;5;250m3
[39m Lundberg et al. (2020) Increased serum osteocalcin levels and vitamin K statu…
#> 
[38;5;250m4
[39m Henriquez Parodi et al. (2021) Kavli Selling Cheese in a Tube to the World    
#> 
[38;5;250m5
[39m Gaber et al. (2021) Manufacture and characterization of acid-coagulated fresh…
#> 
[38;5;250m6
[39m Wolka et al. (2021) Soil organic carbon and associated soil properties in Ens…

In this second example we have decided that 2019 was an even better year for cheese and change the published_since argument to 2019. The reason why we are doing this is to demonstrate the duplicate function in Cristin, which will identify any Cristin references imported into Zotero if zotero.check = TRUE. The function will try to use the zotero argument to search the specified collection(s).

# Query Cristin (again)
cristin2 <- Cristin(
  title = "cheese",
  published_since = 2019,
  published_before = 2021,
  filter = cristin.filter,
  zotero.import = TRUE,
  zotero.check = TRUE,
  use.identifiers = FALSE,
  zotero = post.cristin
)
#> Found 21 results 
#> Checking whether references exist in library 
#> Removed 6 duplicates 
#> Checking whether references are supported. See `CristinSupported()` 
#> Looking for missing data 
#> Filtered out 9 results 
#> Converting 6 references from Cristin to Zotero format 
#> 
——————Next ID: 1721342. Progress: 16.67% (1/6). ETA: 17.02.2024 - 21:31:38——————
——————Next ID: 1678430. Progress: 33.33% (2/6). ETA: 17.02.2024 - 21:31:38——————
——————Next ID: 1709922. Progress: 50.00% (3/6). ETA: 17.02.2024 - 21:31:38——————
——————Next ID: 1668352. Progress: 66.67% (4/6). ETA: 17.02.2024 - 21:31:38——————
——————Next ID: 1715026. Progress: 83.33% (5/6). ETA: 17.02.2024 - 21:31:38——————
—————————————————Process: 100.00% (6/6). Elapsed time: 00:00:01—————————————————

Adding items from identifiers

Sometimes, it happens to everybody, we’re having a bunch of ISBNs and DOIs lying around. Luckily, we can easily add them to Zotero using the Zotero wrapper. In this case we are adding them to the collection “quick”.

# ISBN
isbn.items <- c("9780761973836", "9788215048451")

# DOI
doi.items <- c("https://doi.org/10.31234/osf.io/venu6", 
               "10.1177/1098214010376532")

# Post the items to the collections called quick
identifiers <- Zotero(
  collection.names = c("The", "quick"),
  isbn = isbn.items,
  doi = doi.items,
  library = TRUE,
  index = TRUE,
  post = TRUE,
  post.collections = FALSE,
  post.items = TRUE,
  silent = TRUE,
  get.items = FALSE
)

# Select only names in index and print
identifiers$index |> 
  dplyr::select(name) |>
  print(width = 80)
#> # A tibble: 4 × 1
#>   name                                                                          
#>   <chr>                                                                         
#> 1 Ong-Dean et al. (2011) Challenges and Dilemmas in Implementing Random Assignm…
#> 2 Wijeakumar et al. (2020) Home assessment of visual working memory in pre-scho…
#> 3 Field & Hole (n.d.) How to design and report experiments                      
#> 4 Grøholt et al. (2022) Lærebok i barne- og ungdomspsykiatri

Adding items from the Man

When working in academia within a Norwegian context, you will frequently need to examine the musings of politicians, or their selected group of researchers. So, let’s add some random white papers and official reports using ZoteroGov.

# Combine to a single tibble using dplyr
gov.items <- dplyr::bind_rows(
  # Find some random white papers
  ZoteroGov(c("26 (2001-2002)", "31 (2014-2015)"), type = "meldst")$data,
  # Finds some random official Norwegian reports.
  ZoteroGov(c("2014: 4", "2018: 2"), type = "nou")$data
)

# Post the items to the collection called brown
## Fitting given the source
the.man <- Zotero(
  collection.names = c("The", "quick", "brown"),
  metadata = gov.items,
  library = TRUE,
  index  = TRUE,
  post = TRUE,
  post.collections = FALSE,
  post.items = TRUE,
  silent = TRUE,
  get.items = FALSE
)

# Select only names in index and print
the.man$index |> 
  dplyr::select(name) |>
  print(width = 80)
#> # A tibble: 4 × 1
#>   name                                                                          
#>   <chr>                                                                         
#> 1 St.meld. nr. 26 (2001-2002) Bedre kollektivtransport                          
#> 2 NOU 2018: 2 (2018) Fremtidige kompetansebehov I — Kunnskapsgrunnlaget         
#> 3 Meld. St. 31 (2014–2015) Garden som ressurs – marknaden som mål — Vekst og gr…
#> 4 NOU 2013: 4 (2013) Kulturutredningen 2014

Adding items from CRAN

Show some love (citations are love, yes?) for the authors that create the packages that you use! (We see you, Hadley Wickham).

Here, we’re using the ZoteroCran function to collect metadata from The Comprehensive R Archive Network (CRAN). The collection.key is defined as:

lazy <- collections$collections |> 
  dplyr::filter(name == "lazy") |> 
  dplyr::pull("key")

The key is “7ZAXF9MY” and the argument recursive = TRUE, thus the function is looking for the specified collection (i.e., “lazy”) and all its children (i.e., “dog”).

It is often useful to link items to several (nested) collections. For instance, you can create the nested collections “project -> CRAN-packages” and link the items to both collections. You can then choose to access all items under “project”, including those in “CRAN-packages”, or just access those filed under the latter.

# Find selected packages 
packages <- c(
  "dplyr",
  "httr",
  "jsonlite",
  "purrr",
  "rvest",
  "rlang",
  "tibble",
  "tidyr",
  "tidyselect"
)

# Post the items to the collections called lazy and dog
cran <- Zotero(
  collection.key = lazy,
  metadata = ZoteroCran(packages)$data,
  library = TRUE,
  index = TRUE,
  recursive = TRUE,
  post = TRUE,
  post.collections = FALSE,
  post.items = TRUE,
  silent = FALSE,
  get.items = FALSE
)
#> Searching for collections 
#> Found 9 collections 
#> The Zotero list contains: 2 collections, 0 items, and 0 attachments 
#> Adding 9 items to library using 1 POST request 
#> 
—————————————————Process: 100.00% (1/1). Elapsed time: 00:00:00—————————————————
#> $post.status.items
#> 
[38;5;246m# A tibble: 9 × 2
[39m
#>   status  key     
#>   
[3m
[38;5;246m<fct>
[39m
[23m   
[3m
[38;5;246m<chr>
[39m
[23m   
#> 
[38;5;250m1
[39m success T9BH4V3Y
#> 
[38;5;250m2
[39m success XNEEYIEW
#> 
[38;5;250m3
[39m success B5VAQTSF
#> 
[38;5;250m4
[39m success K9SJD2I2
#> 
[38;5;250m5
[39m success APKKDDWH
#> 
[38;5;250m6
[39m success 6VZGDS6G
#> 
[38;5;250m7
[39m success V7LIR547
#> 
[38;5;250m8
[39m success BCK9N2VE
#> 
[38;5;250m9
[39m success VV4E43JN
#> 
#> $post.summary.items
#> 
[38;5;246m# A tibble: 1 × 2
[39m
#>   status  summary
#>   
[3m
[38;5;246m<fct>
[39m
[23m     
[3m
[38;5;246m<int>
[39m
[23m
#> 
[38;5;250m1
[39m success       9
#> 
#> 
#> Creating index for items

# What do we got?
# Select only names in index and print
index <- cran$index |> 
  dplyr::select(name) |>
  print(width = 80)
#> 
[38;5;246m# A tibble: 9 × 1
[39m
#>   name                                                                          
#>   
[3m
[38;5;246m<chr>
[39m
[23m                                                                         
#> 
[38;5;250m1
[39m Wickham (2023a) dplyr: A Grammar of Data Manipulation                         
#> 
[38;5;250m2
[39m Wickham (2023b) httr: Tools for Working with URLs and HTTP                    
#> 
[38;5;250m3
[39m Ooms (2023) jsonlite: A Simple and Robust JSON Parser and Generator for R     
#> 
[38;5;250m4
[39m Wickham (2023c) purrr: Functional Programming Tools                           
#> 
[38;5;250m5
[39m Henry (2024) rlang: Functions for Base Types and Core R and 'Tidyverse' Featu…
#> 
[38;5;250m6
[39m Wickham (2024a) rvest: Easily Harvest (Scrape) Web Pages                      
#> 
[38;5;250m7
[39m Müller (2023) tibble: Simple Data Frames                                      
#> 
[38;5;250m8
[39m Wickham (2024b) tidyr: Tidy Messy Data                                        
#> 
[38;5;250m9
[39m Henry (2022) tidyselect: Select from a Set of Strings

Copying

There are many situations where we want to copy collections and items (and attachments) between libraries. You may want to copy from a public Zotero library to your own or share your collections with a research group.

The Zotero function contains a (somewhat convoluted) method of copying collections and items from a group library to a user library (and vice versa). However, in this example we’re appending the group collections to our long line of nested collections, making the process somewhat messy. Therefore, we split up the process using the ZoteroCopy and ZoteroPost functions. The change.library argument will query Zotero and alter the location according to specified coordinates (the default location is the user library as defined in .Renviron).

Please see the tutorial on how to set up user/group id and API keys.

# Access the group library (defined in .Renviron)
group.library <- Zotero(
  user = FALSE,
  library = TRUE,
  silent = FALSE
)
#> Searching for collections 
#> Found 5 collections 
#> The Zotero list contains: 5 collections, 0 items, and 0 attachments 
#> Searching for all items in library 
#> Found 3 items 
#> The Zotero list contains: 5 collections, 3 items, and 0 attachments

# Copy the library creating new keys 
copy.library <- ZoteroCopy(
  zotero = group.library,
  change.library = TRUE,
  silent = FALSE
)
#> Copying collections 
#> Copying items

# Find key for dog
dog <- collections$collections |> 
  dplyr::filter(name == "dog") |> 
  dplyr::pull("key")

# Change parent collection of top-level collection to dog
copy.library$collections <- copy.library$collections |>
  dplyr::mutate(
    parentCollection = dplyr::case_when(
      parentCollection == "FALSE" ~ dog,
      TRUE ~ parentCollection
    )
  )

# Copy the collection and items to the user library
post.copy <- ZoteroPost(
  zotero = copy.library,
  silent = TRUE
)

# Select only names in index and print
ZoteroIndex(post.copy$items) |>
  dplyr::select(name) |>
  print(width = 80)
#> # A tibble: 3 × 1
#>   name                                                                          
#>   <chr>                                                                         
#> 1 Mishra et al. (2023) Exploring Active and Critical Engagement in Human-Robot …
#> 2 Somby & Stalheim (2023) Vygotsky med VR-briller                               
#> 3 Somby & Vik (2023) Vygotskys defektologi - et perspektiv på inkluderende oppl…

Bibliography

It’s a wrap!

Now it’s time to harvest the bounty that we have created during the steps above. We could create separate bibliographies, but for the sake of simplicity we are mashing it all together. That’s friendship!

The arguments used in Zotero are somewhat self-explanatory, however, we are also csl.type to access a style repository in order to create a Citation Style Language (CSL) file according to APA7.

# Create references.bib in biblatex format with style.csl according to APA7
bibliography <- Zotero(
  collection.names = "The",
  recursive = TRUE,
  library = TRUE,
  export = TRUE,
  format = "biblatex",
  save.data = TRUE,
  save.path = tempdir(),
  bib.name = "references",
  csl.type = "apa-single-spaced",
  csl.name = "style",
  silent = TRUE
)

# What do we got?
sprintf(
  "We now have %s collections, %s references, and %s attachments",
  bibliography$n.collections,
  bibliography$n.items,
  bibliography$n.attachments
)
#> [1] "We now have 9 collections, 23 references, and 0 attachments"

Deleting

Housecleaning! We should clean up the mess in your library. You could set ragnarok and force = TRUE, if you want to delete everything in your library.

# Delete all collections and items belonging to initial key
delete <- ZoteroDelete(
  bibliography, 
  delete.collections = TRUE,
  delete.items = TRUE
)
#> Deleting 9 collections using 1 DELETE request 
#> 
—————————————————Process: 100.00% (1/1). Elapsed time: 00:00:00—————————————————
#> Deleting 23 items using 1 DELETE request 
#> 
—————————————————Process: 100.00% (1/1). Elapsed time: 00:00:00—————————————————

References

We can now display the references that we have collected and created using c2z.

Field, A. P., & Hole, G. (n.d.). How to design and report experiments. Sage publications Ltd.
Gaber, S. M., Johansen, A.-G., Devold, T. G., Rukke, E.-O., & Skeie, S. B. (2021). Manufacture and characterization of acid-coagulated fresh cheese made from casein concentrates obtained by acid diafiltration. Journal of Dairy Science, 104(6), 6598–6608. https://doi.org/10.3168/jds.2020-19917
Grøholt, B., Weidle, B., Garløv, I., & Ramleth, R.-K. (2022). Lærebok i barne- og ungdomspsykiatri (6th ed.). Universitetsforlaget.
Henriquez Parodi, M. C., Lankut, E., & Alon, I. (2021). Kavli selling cheese in a tube to the world. In M. Chavan & L. Taksa (Eds.), Intercultural management in practice: Learning to lead diverse global organizations (p. 27). Emerald Group Publishing Limited. https://doi.org/10.1108/978-1-83982-826-320211004
Henry, L. (2022). Tidyselect: Select from a set of strings (Version 1.2.0). https://cran.r-project.org/package=tidyselect
Henry, L. (2024). Rlang: Functions for base types and core r and ’tidyverse’ features (Version 1.1.3). https://cran.r-project.org/package=rlang
Lundberg, H. E., Holand, T., Holo, H., & Larsen, S. (2020). Increased serum osteocalcin levels and vitamin k status by daily cheese intake. International Journal of Clinical Trials, 7(2), 55. https://doi.org/10.18203/2349-3259.ijct20201712
Lundberg, H. E., Holo, H., Holand, T., Fagertun, H. E., & Larsen, S. (2021). Determination of maintenance jarlsberg® cheese dose to keep the obtained serum osteocalcin level; a response surface pathway designed de-escalation dose study with individual starting values. International Journal of Clinical Trials, 8(3), 174. https://doi.org/10.18203/2349-3259.ijct20212841
Meld. St. 31 (2014–2015). (2015). Garden som ressurs – marknaden som mål — Vekst og gründerskap innan landbruksbaserte næringar. Landbruks- og matdepartementet. https://www.regjeringen.no/id2415017
Müller, K. (2023). Tibble: Simple data frames (Version 3.2.1). https://cran.r-project.org/package=tibble
NOU 2013: 4. (2013). Kulturutredningen 2014. Kulturdepartementet. https://www.regjeringen.no/id715404
NOU 2018: 2. (2018). Fremtidige kompetansebehov I — Kunnskapsgrunnlaget. Kunnskapsdepartementet. https://www.regjeringen.no/id2588070
Olsen, M. A., Vhile, S. G., Porcellato, D., Kidane, A., & Skeie, S. B. (2021). Feeding concentrates with different protein sources to high-yielding, mid-lactation norwegian red cows: Effect on cheese ripening. Journal of Dairy Science, 104(4), 4062–4073. https://doi.org/10.3168/jds.2020-19226
Ong-Dean, C., Huie Hofstetter, C., & Strick, B. R. (2011). Challenges and dilemmas in implementing random assignment in educational research. American Journal of Evaluation, 32(1), 29–49. https://doi.org/10.1177/1098214010376532
Ooms, J. (2023). Jsonlite: A simple and robust JSON parser and generator for r (Version 1.8.8). https://cran.r-project.org/package=jsonlite
St.meld. nr. 26 (2001-2002). (2002). Bedre kollektivtransport. Samferdselsdepartementet. https://www.regjeringen.no/id196149
Wickham, H. (2023a). Dplyr: A grammar of data manipulation (Version 1.1.4). https://cran.r-project.org/package=dplyr
Wickham, H. (2023b). Httr: Tools for working with URLs and HTTP (Version 1.4.7). https://cran.r-project.org/package=httr
Wickham, H. (2023c). Purrr: Functional programming tools (Version 1.0.2). https://cran.r-project.org/package=purrr
Wickham, H. (2024a). Rvest: Easily harvest (scrape) web pages (Version 1.0.4). https://cran.r-project.org/package=rvest
Wickham, H. (2024b). Tidyr: Tidy messy data (Version 1.3.1). https://cran.r-project.org/package=tidyr
Wijeakumar, S., Rafetseder, E., Shing, Y. L., & McKay, C. (2020). Home assessment of visual working memory in pre-schoolers reveals associations between behaviour, brain activation and environmental measures. PsyArXiv. https://doi.org/10.31234/osf.io/venu6
Wolka, K., Biazin, B., Martinsen, V., & Mulder, J. (2021). Soil organic carbon and associated soil properties in enset (ensete ventricosum welw. Cheesman)-based homegardens in ethiopia. Soil and Tillage Research, 205, 104791. https://doi.org/10.1016/j.still.2020.104791