Extraction et visualisation des données du Ciné-journal suisse

Le script R vous permet d’extraire les données du Ciné-journal suisse par l’intermédiaire de l’interface de programmation d’application (API) et de les visualiser.

Chargement des bibliothèques requises

Pour permettre le chargement correct des bibliothèques requises, installez-les dans votre environnement R si ce n’est pas déjà fait. Remarque : vous devez avoir préalablement installé la version 4.0.x ou ultérieure de R et la version 1.12.x ou ultérieure du package data.table. Vous pouvez ensuite charger toutes les bibliothèques à l’aide du code suivant :

 

library(httr)
library(jsonlite)
library(data.table)
library(stringr)
library(ggplot2)
library(magrittr)
library(svglite)
library(foreach)
library(doParallel)
corenum <- detectCores()

 

La dernière ligne indique le nombre de cœurs de calcul présents sur votre ordinateur. Nous utiliserons cette fonction plus tard dans les processus parallélisés gérés par les packages foreach et doParallel.

Extraction des données à l’aide de requêtes dans l’API

Comme il n’est possible d’obtenir que 100 résultats par requête, l’extraction des données peut prendre un certain temps. Pour obtenir plus de 20 000 résultats, il faudra donc effectuer plus de 200 requêtes individuelles dans l’API ! Si les requêtes web sont peu coûteuses au niveau informatique, elles sont toutes inévitablement ralenties par le trafic web. Afin d’accélérer considérablement la procédure, il faut paralléliser le processus à l’aide du package foreach. À noter que l’API limite actuellement les requêtes à partir du 9999e résultat de recherche. Par conséquent, il faut effectuer des boucles d’extraction de données distinctes pour chaque décennie, ce qui nous permet d’obtenir une boucle for parallélisée et imbriquée. À la fin de ce bloc de code, on sauvegarde les données extraites dans un fichier local appelé FilmWochenSchau.RData afin d’éviter de devoir effectuer de nouvelles requêtes dans l’API si l’on modifie par erreur la table de données.

 

# The request function ----
makerequest <- function(skip,take,decade) {
  jsonrequest <- paste0('{
    "query":{
      "searchGroups":[
        {"searchFields":[
          {"key":"referenceCode","value":"J2.143#1996/386*"},
          {"key": "creationPeriod","value": "',decade,'-',decade+10,'"}
        ],"fieldOperator":1}
      ],
      "groupOperator":1
    },
    "paging":{"skip":', skip ,',"take":', take ,',"orderBy":"","sortOrder":""},
    "facetsFilters": [
      {
        "filters": ["level:\\"Dokument\\""],
        "facet": "level"
      },
      {
        "filters": [
          "aggregationFields.bestand:\\"Stiftung Schweizer Filmwochenschau (1942-1975)\\""
        ],
        "facet": "aggregationFields.bestand"
      }
    ]
  }')
  POST(
    url,
    body = minify(jsonrequest), 
    encode = "raw",
    content_type_json()
  )
}

url <- "https://www.recherche.bar.admin.ch/recherche/api/v1/entities/Search"

# Fetch all data by bunches of 100 ----
decadecounts <- data.table(decade = seq(1940,1970,by=10))
decadecounts[,count:= sapply(decade, function(x){
    res <- makerequest(1,1,x)
    fws <- content(res)
    fws$entities$paging$total
})]
cl <- parallel::makeCluster(corenum) 
doParallel::registerDoParallel(cl)
fws.datatable <- foreach(
  decade=decadecounts$decade,
  count=decadecounts$count,
  .packages = c("jsonlite","httr","data.table","magrittr"),
  .verbose = TRUE
  ) %:%
    foreach(
      i=seq(1, ceiling(count/100)*100, by = 100), 
      .combine=function(a,b)rbindlist(list(a,b))
    ) %dopar% {
      res <- makerequest(i,100,decade)
      fws <- content(res)
      data.table(
        refCode = sapply(fws$entities$items,function(x) return(x$archiveRecordId)),
        archiveID = sapply(fws$entities$items,function(x) return(x$referenceCode)),
        date = sapply(fws$entities$items,function(x) return(x$creationPeriod$text)),
        title = sapply(fws$entities$items,function(x) return(x$title)),
        dauer = sapply(fws$entities$items,function(x) return(x$customFields$format %>% unlist)),
        url = sapply(fws$entities$items,function(x) return(x$customFields$digitaleVersion %>% unlist %>% .["url"])),
        thema = sapply(fws$entities$items, function(x) x$customFields$thema %>% unlist)
      ) 
} %>% rbindlist(fill=TRUE)

save(fws.datatable,file="FilmWochenSchau.RData")

 

 

Extraction de variables à l’aide d’expressions régulières

Le champ « thema » de l’API de recherche contient plusieurs valeurs que l’on peut extraire et classer dans des colonnes individuelles à l’aide d’expressions régulières (RegEx). Cette étape permet également de nettoyer la structure des données extraites.

 

unlistColumn <- function(column) {
  sapply(column, function(x) {
    if (!is.null(unlist(x))) {return(unlist(x))} else return(NA)
  })
}
fws.datatable$refCode <- unlistColumn(fws.datatable$refCode)
fws.datatable$archiveID <- unlistColumn(fws.datatable$archiveID)
fws.datatable$date <- unlistColumn(fws.datatable$date)
fws.datatable$title <- unlistColumn(fws.datatable$title)
fws.datatable$dauer <- unlistColumn(fws.datatable$dauer)
fws.datatable$url <- unlistColumn(fws.datatable$url)
fws.datatable$thema <- unlistColumn(fws.datatable$thema)

# Filter duplicates
fws.datatable <- unique(fws.datatable, by="refCode") 

# Then apply RegEx
fws.datatable[,thema_orte := sapply(thema, function(x) {str_match(x, "Orte:[\\r\\n ]{1,3}([^\\r\\n]*)") %>% .[2]})]
fws.datatable[,thema_schlagworte := sapply(thema, function(x) {str_match(x, "Schlagworte:[\\r\\n ]{1,3}([^\\r\\n]*)") %>% .[2]})]
fws.datatable[,dauer_dauer := sapply(dauer, function(x) {str_match(x, "Dauer: ([0-9:]*)") %>% .[2]})]
fws.datatable[,dauer_seconds := sapply(dauer_dauer, function(x) {
  ifelse(
    str_count(x,":")>1,
    as.difftime(x, format = "%H:%M:%S", units = "secs") %>% strtoi,
    as.difftime(x, format = "%M:%S", units = "secs") %>% strtoi
  )
})]
fws.datatable[,date:=as.Date(date,"%d.%m.%Y")]

 

 

Visualisation des données

Le script suivant recense le nombre d’émissions diffusées par mois et les présente sous la forme de diagrammes « en rose », développés par Florence Nightingale (infirmière et statisticienne, 1820-1910). La représentation graphique, qui figure en haut de cette page, vous permet d’avoir une vue d’ensemble du nombre d’éditions mensuelles du Ciné-journal suisse. Le script l’enregistre automatiquement au format SVG (fichier graphique vectoriel).

 

emissions_par_date <- fws.datatable[, .N, by=date][, c("date","count","year","month", "N") := .(date,N,format(date,"%Y"),as.integer(format(date,"%m")), NULL)]
emissions_par_month <- emissions_par_date[, .(count=sum(count)), by=.(month,year)]
ggplot(emissions_par_month) +
  geom_col(aes(x=month,y=count),colour="white", fill="darkred", size=0.1) + 
  scale_x_continuous(
    breaks=c(3,6,9,12),
    minor_breaks = c(1,2,4,5,7,8,10,11)
  ) +
  coord_polar(start=pi/12) +
  facet_wrap(~year,ncol=10) +
  labs(title = "Ciné-Journal suisse",
       subtitle = "Nombre d'émissions diffusées par année et mois",
       caption = "Source: Archives fédérales suisses", 
       x = NULL, y = NULL) 
ggsave("cinejournal.svg",width=15,height=8)

 

 

Extraction de données supplémentaires pour chaque document

Pour extraire certaines données relatives à un document unique, il faut effectuer une requête différente dans l’API, qui permet d’obtenir tous les détails concernant un ID d’enregistrement spécifique. Le code suivant permet d’extraire le champ « darin » et de décomposer son contenu à l’aide de RegEx pour connaître le « genre » de chaque document :

 

getDescription <- function(id) {
  resget <- GET(
    paste0("https://www.recherche.bar.admin.ch/recherche/api/v1/entities/",id), 
    encode = "json",
    content_type_json()
  )
  content(resget)
}

sequence <- c(seq(1, ceiling(fws.datatable%>%nrow/5000)*5000, by = 5000),fws.datatable %>% nrow)
for (i in 1:(sequence%>%length-1)){
  cl <- parallel::makeCluster(corenum) 
  doParallel::registerDoParallel(cl)
  fws.datatable[sequence[i]:sequence[i+1], darin := foreach(
    i = refCode,
    .export = "getDescription",
    .packages = c("jsonlite","httr","magrittr"),
    .verbose = TRUE
  ) %dopar% {
    x = getDescription(i)
    x[["withinInfo"]] %>% unlist
  }
  ]
  stopCluster(cl)
}
fws.datatable[,description_genre := sapply(darin, function(x) {str_match(x, "Genre:[\\r\\n ]{1,2}([^\\r\\n]*)") %>% .[2]})]
fws.datatable[,description_inhaltsangabe := sapply(darin, function(x) {str_match(x, "Inhaltsangabe:[\\r\\n ]{1,2}([^\\r\\n]*)") %>% .[2]})]
fws.datatable[,description_inhaltsangabe_ort := sapply(description_inhaltsangabe, function(x) {str_match(x, "^([^:]*)") %>% .[2]})]
ggplot(fws.datatable[,.N,by=description_genre]) + geom_col(aes(x=description_genre,y=N)) + coord_flip()
ggsave("cinejournal_genre.svg",width=9,height=4)

 

 

Retrouvez le code R complet sur GitHub et participez vous aussi !

Le code R complet  se trouve dans notre dépôt GitHub.

Vous pouvez utiliser les données extraites pour réaliser bien d’autres représentations graphiques et analyses.

Alors n’attendez plus : créez votre propre fork !