# General
library(tidyverse)
# Webscraping
library(rvest)
library(RSelenium)
# Geo data
library(tidygeocoder)
library(leaflet)
library(rnaturalearth)
library(sf)
# NLP
library(udpipe)
library(textrank)
library(wordcloud)
# Cleaning
library(stringr)
# Additional functions presented at the end of the post
source('scraping_functions.R')
Il y a quelques semaines, j’ai commencé à chercher un poste de data scientist dans l’industrie. Mes premières actions ont été de :
- Regarder les offres d’emploi sur des sites comme Indeed
- Mettre à jour mon CV
Après avoir lu de nombreuses annonces et travaillé plusieurs heures sur mon CV, je me suis demandé si je pouvais optimiser ces étapes avec R et Data Science. J’ai donc décidé de scraper Indeed et d’analyser les données des offres de data science pour :
- Obtenir une vue d’ensemble visuelle des informations essentielles comme la localisation, le type de contrat, la fourchette salariale pour un grand nombre d’annonces
- Optimiser mon CV pour le scan ATS avec des mots-clés précis
Chargement des bibliothèques
La première étape est d’importer plusieurs packages :
Collecter les données avec le web scraping
Au début de ce projet, j’utilisais read_html()
de rvest pour accéder et télécharger la page web d’Indeed. Cependant, les pages Indeed sont protégées par un logiciel anti-scraping qui bloquait toutes mes demandes, même si le scraping n’est pas interdit sur les pages qui m’intéressent (j’ai vérifié la page robots.txt).
C’est pourquoi j’ai décidé d’accéder aux pages avec Rselenium qui permet d’exécuter un navigateur sans tête (“headless”). On commence par naviguer vers la page correspondant aux résultats de recherche des offres de Data Scientist en France :
= "https://fr.indeed.com/jobs?q=data%20scientist&l=France&from=searchOnHP"
url
# Headless Firefox browser
<- list("moz:firefoxOptions" = list(args = list('--headless')))
exCap <- rsDriver(browser = "firefox", extraCapabilities = exCap, port=1111L,
rD verbose = F)
<- rD$client
remDr
# Navigate to the url
$navigate(url)
remDr
# Store page source
<- remDr$getPageSource(header = TRUE)[[1]] %>% read_html() web_page
Pour scraper une information spécifique sur une page web, voici les étapes à suivre :
- Trouver l’élément/texte/donnée que vous souhaitez scraper sur la page web.
- Trouver le xpath ou le sélecteur CSS associé en utilisant l’outil de développement de Chrome ou Firefox (tutoriel ici !).
- Extraire l’élément avec
html_element()
en indiquant le xpath ou le sélecteur CSS. - Transformer les données en texte avec
html_text2()
. - Nettoyer les données si nécessaire.
Voici l’exemple avec le nombre d’offres de Data Scientist listées en France :
%>%
web_page html_element(css = "div.jobsearch-JobCountAndSortPane-jobCount") %>% # selecting with css
html_text2() %>% # Transform to text
str_remove_all("[^0-9.-]") %>% # Clean the data to only get numbers
substr(start = 2, stop = 8) %>%
as.numeric()
Pour l’instant, on ne peut scraper les données que de la première page. Cependant, je suis intéressé par toutes les offres d’emploi et j’ai besoin d’accéder aux autres pages ! Après avoir navigué sur les 3 premières pages des offres, j’ai remarqué un modèle dans l’URL (valide au moment de l’écriture), ce qui signifie qu’avec une seule ligne de code, je peux produire une liste contenant les URL des 40 premières pages.
Une fois la liste obtenue, il ne reste plus qu’à boucler sur toutes les URL avec un délai (bonne pratique pour le web scraping), collecter les données et les nettoyer avec des fonctions personnalisées (à la fin de l’article) :
# Creating URL link corresponding to the first 40 pages
= "https://fr.indeed.com/jobs?q=data%20scientist&l=France&start="
base_url <- c(url, paste0(base_url, as.character(seq(from=10, to=400, by=10))))
url_list
# Looping through the URL list
<- list()
res for(i in 1:length(url_list)){
# Navigate to the URL
$navigate(url_list[i])
remDr
# Store page source
<- remDr$getPageSource(header = TRUE)[[1]] %>% read_html()
web_page
# Job title
<- web_page %>%
job_title html_elements(css = ".mosaic-provider-jobcards .result") %>%
html_elements(css = ".resultContent") %>%
html_element("h2") %>%
html_text2() %>%
str_replace(".css.*;\\}", "")
# URL for job post
<- web_page %>%
job_url html_elements(css = ".mosaic-provider-jobcards .result")%>%
html_elements(css = ".resultContent") %>%
html_element("h2") %>%
html_element("a") %>%
html_attr('href') %>%
lapply(function(x){paste0("https://fr.indeed.com", x)}) %>%
unlist()
# Data about company
<- web_page %>%
company_info html_elements(css = ".mosaic-provider-jobcards .result")%>%
html_elements(css = ".resultContent")%>%
html_element(css = ".company_location")%>%
html_text2() %>%
lapply(FUN = tidy_comploc) %>% # Function to clean the textual data
do.call(rbind, .)
# Data about job description
<- web_page %>%
job_desc html_elements(css = ".mosaic-provider-jobcards .result")%>%
html_element(css =".slider_container .jobCardShelfContainer")%>%
html_text2() %>%
tidy_job_desc() # Function to clean the textual data related to job desc.
# Data about salary (when indicated)
<- web_page %>%
salary_hour html_elements(css = ".mosaic-provider-jobcards .result .resultContent")%>%
html_element(css = ".salaryOnly") %>%
html_text2() %>%
lapply(FUN = tidy_salary) %>% # Function to clean the data related to salary
do.call(rbind, .)
# Job posts in the same format
<- cbind(job_title, company_info, salary_hour, job_desc, job_url)
final_df colnames(final_df) <- c("Job_title", "Company", "Location", "Rating", "Low_salary", "High_salary", "Contract_info", "Job_desc", "url")
<- final_df
res[[i]]
# Sleep 5 seconds, good practice for web scraping
Sys.sleep(5)
}
# Gather all the job post in a tibble
<- as_tibble(do.call("rbind", res))
final_df
# Final data cleaning
<- final_df %>%
final_df mutate_at(c("Rating", "Low_salary", "High_salary"), as.numeric)
# Clean job title
$Job_title_c <- clean_job_title(final_df$Job_title)
final_df$Job_title_c <- as.factor(final_df$Job_title_c) final_df
On a maintenant un jeu de données propre ! Voici un exemple tronqué des 5 premières lignes :
Job_title | Company | Location | Rating | Low_salary | High_salary | Contract_info | Job_desc | Job_type | Job_title_c |
---|---|---|---|---|---|---|---|---|---|
Data Scientist junior (H/F) | Kea & Partners | 92240 Malakoff | NA | 3750 | 4583 | CDI +2 | Travail en journée +1 | Plusieurs postes à pourvoirMaitrise de Python et des packages de data science. 1er cabinet européen de conseil en stratégie à devenir Société à Mission, certifiés B-Corp depuis 2021*,… | Présentiel | data scientist junior |
Data Scientist (F ou H) | SNCF | Saint-Denis (93) | 3.9 | NA | NA | CDI | Le développement informatique (C, C++, Python, Azure, …). Valider et recetter les phases des projets. Travailler avec des méthodes agiles avec les équipes et… | Présentiel | data scientist |
Data Scientist (H/F) (IT) | Yzee Services | Paris (75) | 3.3 | 2916 | 3750 | Temps plein | Recueillir, structurer et analyser les données pertinentes pour l'entreprise (activité liée à la relation client, conseil en externe). | Présentiel | data scientist |
Data Scientist H/F | Natan (SSII) | Paris (75) | NA | 4583 | 5833 | CDI +1 | Travail en journée | Plusieurs postes à pourvoirVous retrouverez une *ESN ambitieuse portée par le goût de l’excellence.*. Au sein du département en charge d'automatisation transverse des besoins de la… | Présentiel | data scientist |
Data Scientist Junior H/F / Freelance | karma partners | Roissy-en-Brie (77) | NA | 400 | 550 | Temps plein +1 | Le profil recherché est un profil junior (0-2 ans d'expérience) en data science, avec une appétence technique et des notions d'architecture logicielle et de… | Présentiel | data scientist junior |
Visualisation des salaires proposés
Voyons si on peut obtenir quelques informations sur les offres de jobs en data science en réalisant quelques représentations graphiques. La première chose que je voulais savoir, c’était combien les entreprises étaient prêtes à payer pour recruter un candidat en data science. J’ai donc décidé de réaliser quelques graphiques sur la plage de salaires en fonction de l’entreprise et du titre du poste.
Attention !
Les graphiques suivants doivent être pris avec des pincettes, car ils affichent un petit échantillon des données. En effet, le salaire n’était listé que pour 14% des annonces. Les tendances ou informations dans ces graphiques peuvent ne pas être représentatives des entreprises n’ayant pas indiqué leur salaire proposé.
Salaire par entreprise
Le graphique suivant montre les revenus mensuels proposés par certaines entreprises (toutes les entreprises ne listent pas leur salaire proposé) :
# Function to make euro X scale
<- scales::label_dollar(
euro prefix = "",
suffix = "\u20ac",
big.mark = ".",
decimal.mark = ","
)
%>%
final_df filter(Low_salary > 1600) %>% # To remove internships and freelance works
select(Company, Low_salary, High_salary) %>%
group_by(Company) %>%
summarize_if(is.numeric, mean) %>%
mutate(Mean_salary = rowMeans(cbind(Low_salary, High_salary), na.rm = T),
Company = fct_reorder(Company, desc(-Mean_salary))) %>%
ggplot(aes(x = Company)) +
geom_point(aes(y = Mean_salary), colour = "#267266") +
geom_linerange(aes(ymin = Low_salary, ymax = High_salary)) +
geom_hline(aes(yintercept = median(Mean_salary)), lty=2, col='red', alpha = 0.7) +
scale_y_continuous(labels = euro) +
ylab("Monthly income") +
xlab("") +
coord_flip() +
theme_bw(base_size = 8)
La médiane des salaires mensuels est d’environ 3700 euros. Comme vous pouvez le constater, les salaires peuvent varier considérablement selon l’entreprise. Cela est en partie dû au fait que je n’ai pas fait de distinction entre les différents types de postes en data science (data scientist, data analyst, data engineer, senior ou lead).
Salaire par titre de poste
On peut tracer le même graphique, mais au lieu de regrouper par entreprise, on va regrouper par titre de poste :
%>%
final_df filter(Low_salary > 1600) %>% # To remove internships and freelance works
select(Job_title_c, Low_salary, High_salary, Job_type) %>%
group_by(Job_title_c) %>%
summarize_if(is.numeric, ~ mean(.x, na.rm = TRUE)) %>%
mutate(Mean_salary = rowMeans(cbind(Low_salary, High_salary), na.rm = T),
Job_title_c = fct_reorder(Job_title_c, desc(-Mean_salary))) %>%
ggplot(aes(x = Job_title_c, y = Mean_salary)) +
geom_point(aes(y = Mean_salary), colour = "#267266") +
geom_linerange(aes(ymin = Low_salary, ymax = High_salary)) +
#geom_label(aes(label = n, Job_title_c, y = 1500), data = count_df) +
scale_y_continuous(labels = euro) +
theme_bw(base_size = 12) +
xlab("") +
ylab("Monthly Income") +
coord_flip()
On remarque clairement les différences de salaires proposés en fonction du titre de poste : les data scientists semblent gagner légèrement plus en moyenne que les data analysts. Les entreprises semblent également proposer des salaires plus élevés pour les postes avec plus de responsabilités ou nécessitant plus d’expérience (senior, lead).
Salaire en fonction de la localisation : télétravail complet, hybride, sur site ?
Enfin, on peut tracer les salaires en fonction de la localisation (télétravail complet, hybride, sur site) pour voir si cela a un impact :
# Tidy the types and locations of listed jobs
<- tidy_location(final_df)
final_df <- count(final_df %>% filter(Low_salary > 1600), Job_type)
count_df %>%
final_df filter(Low_salary > 1600) %>%
drop_na(Location) %>%
mutate(Mean_salary = rowMeans(cbind(Low_salary, High_salary), na.rm = T),
Job_type = as.factor(Job_type)) %>%
ggplot(aes(x = Job_type, y = Mean_salary)) +
geom_boxplot(na.rm = TRUE) +
geom_label(aes(label = n, Job_type, y = 5500), data = count_df) +
scale_y_continuous(labels = euro) +
theme_bw(base_size = 12) +
xlab("Job Type") +
ylab("Income")
Il est à noter que la plupart des emplois proposés en France sont des emplois sur site. Le salaire médian pour ce type de postes est légèrement inférieur à celui des emplois hybrides. La distribution des salaires pour les emplois en télétravail complet et hybrides doit être interprétée avec prudence car elle ne concerne que 12 offres d’emploi.
Cartographie des lieux des emplois
Lors de ma recherche d’emploi, j’étais frustré de ne pas voir une carte géographique regroupant les lieux de tous les emplois proposés. Une telle carte pourrait m’aider considérablement dans ma recherche. Faisons-la !
Tout d’abord, on doit nettoyer et homogénéiser les lieux pour toutes les offres d’emploi. À cette fin, j’ai créé une fonction personnalisée (tidy_location()
) qui inclut plusieurs fonctions de stringr. Vous pouvez trouver plus de détails sur cette fonction à la fin de ce post. Elle renvoie le lieu sous ce format : [Ville]([Code postal])
. Même si tous les lieux ont été homogénéisés, ils ne peuvent pas être directement tracés sur une carte (on a besoin de la longitude et de la latitude). Pour obtenir la latitude et la longitude à partir du nom de la ville et du code postal, j’ai utilisé la fonction geocode()
du package tidygeocoder.
# Extract coordinates from town name
<- final_df %>%
final_df mutate(Loc_tidy_fr = paste(Loc_tidy, 'France')) %>%
geocode(Loc_tidy_fr, method = 'arcgis', lat = latitude , long = longitude) %>%
select(- Loc_tidy_fr)
Distribution des emplois en Data Science en France
On peut maintenant représenter le nombre d’emplois en Data Science par département :
# Map of France from rnaturalearth package
<- ne_states(country = "France", returnclass = "sf") %>%
france filter(!name %in% c("Guyane française", "Martinique", "Guadeloupe", "La Réunion", "Mayotte"))
# Transform location to st point
<- st_sf(final_df, geom= lapply(1:nrow(final_df), function(x){st_point(c(final_df$longitude[x],final_df$latitude[x]))}))
test st_crs(test) <- 4326
# St_join by departments
<- france %>%
joined st_join(test, left = T)
# Custom breaks for visual representation
= c(0, 2, 5, 10, 30, 50, 100, 260)
my_breaks
%>%
joined mutate(region=as.factor(name)) %>%
group_by(region) %>%
summarize(Job_number=n()) %>%
mutate(Job_number = cut(Job_number, my_breaks)) %>%
ggplot() +
geom_sf(aes(fill=Job_number), col='grey', lwd=0.2) +
scale_fill_brewer("Job number",palette = "GnBu") +
theme_bw()
Il est vraiment intéressant de constater que la répartition des emplois est assez hétérogène en France. La majorité des emplois sont concentrés dans quelques départements qui abritent une grande ville. Cela est attendu, car la plupart des emplois sont proposés par de grandes entreprises souvent installées à proximité des grandes villes.
Carte interactive
On peut assii aller plus loin et tracer une carte interactive avec leaflet, ça nous permet de rechercher dynamiquement une offre d’emploi :
# Plot leaflet map
%>%
final_df mutate(pop_up_text = sprintf("<b>%s</b> <br/> %s",
%>% # Make popup text
Job_title, Company)) leaflet() %>%
setView(lng = 2.36, lat = 46.31, zoom = 5.2) %>% # Center of France
addProviderTiles(providers$CartoDB.Positron) %>%
addMarkers(
popup = ~as.character(pop_up_text),
clusterOptions = markerClusterOptions()
)
Analyser les descriptions d’emploi
De nos jours, la plupart des CV sont scannés et interprétés par un système de suivi des candidatures (ATS). Pour faire simple, ce système recherche des mots-clés dans votre CV et évalue la correspondance avec l’offre d’emploi pour laquelle vous postulez. Il est donc important de décrire vos expériences avec des mots-clés spécifiques pour améliorer vos chances d’accéder à l’étape suivante du processus de recrutement.
Mais quels mots-clés devrait-on inclure dans mon CV ? Répondons à cette question en analysant les descriptions des offres d’emploi de data scientist.
Télécharger et nettoyer chaque description d’emploi
Tout d’abord, on télécharge la description complète de chaque offre en naviguant à travers toutes les URL listées dans notre tableau. On nettoye et homogénéise la description avec une fonction personnalisée :
# Loop through all the URLs
<- list()
job_descriptions <- txtProgressBar(min = 1, max = length(final_df$url), style = 3)
pb for(i in 1:length(final_df$url)){
$navigate(final_df$url[i])
remDr<- remDr$getPageSource(header = TRUE)[[1]] %>% read_html()
web_page <- web_page %>%
job_descriptions[[i]] html_elements(css = ".jobsearch-JobComponent-description") %>%
html_text2()
Sys.sleep(2)
setTxtProgressBar(pb, i)
}# Gathering in dataframe
<- as.data.frame(do.call("rbind", job_descriptions))
job_descriptions names(job_descriptions) <- c("Description")
# Binding to same table:
<- cbind(final_df, job_descriptions)
final_df
# Homogenize with custom function
$Description_c <- lapply(final_df$Description, function(x){clean_job_desc(x)[[2]]})
final_df$Language <- textcat::textcat(final_df$Description) final_df
Procédure d’annotation avec le package udpipe
Cette partie est inspirée de cet article.
Maintenant que les descriptions de tous les emplois listés sont importées et pré-nettoyées, on peut annoter les données textuelles avec le package udpipe. Ce package contient des fonctions et des modèles qui permettent de réaliser la tokenisation, la lemmatisation et l’extraction de mots-clés.
On restreigne d’abord cette analyse aux offres d’emploi de data scientist rédigées en français, puis on annote toutes les descriptions :
# Restricting the analysis to Data scientist post written in french
<- final_df %>%
desc_data_scientist filter((Job_title_c == "data scientist") & (Language == "french")) %>%
select(Description_c)
<- udpipe_download_model(language = "french") # Download the model if necessary
ud_model <- udpipe_load_model(ud_model$file_model)
ud_model
# Annotate the descriptions
<- udpipe_annotate(ud_model, x = paste(desc_data_scientist, collapse = " "))
x <- as.data.frame(x) x
Les noms les plus courants
On peut visualiser les mots les plus utilisés dans les offres d’emploi de data scientist rédigées en français :
<- subset(x, upos %in% "NOUN")
stats <- txt_freq(x = stats$lemma)
stats
%>%
stats top_n(50, freq) %>%
mutate(key = as.factor(key),
key = fct_reorder(key, freq)) %>%
ggplot(aes(x = key, y = freq)) +
geom_bar(stat = 'identity') +
coord_flip() +
ylab("Most common nouns") +
theme_bw()
Bien que cela nous donne une idée des mots à inclure, ce n’est pas très informatif car les mots-clés sont souvent composés de deux mots ou plus.
Extraction des mots-clés pour la rédaction de CV
Il existe plusieurs méthodes implémentées dans udpipe pour extraire les mots-clés d’un texte. Après avoir testé plusieurs méthodes, j’ai sélectionné l’extraction automatique rapide des mots-clés (RAKE) qui me donne les meilleurs résultats :
<- keywords_rake(x = x,
stats term = "token",# Search on token
group = c("doc_id", "sentence_id"), # On every post
relevant = x$upos %in% c("NOUN", "ADJ"), # Only among noun and adj.
ngram_max = 2, n_min = 2, sep = " ")
<- subset(stats, stats$freq >= 5 & stats$rake > 3)
stats
%>%
stats arrange(desc(rake)) %>%
head()
keyword ngram freq rake
1 intelligence artificielle 2 9 9.368889
2 tableaux bord 2 5 8.504274
3 formation supérieure 2 5 8.374725
4 modèles prédictifs 2 15 7.581294
5 force proposition 2 6 7.190238
6 production échelle 2 5 7.034038
wordcloud(words = stats$keyword, freq = stats$freq, min.freq = 3,
max.words=100, random.order=FALSE, rot.per=0.35,
colors=brewer.pal(8, "Dark2"), scale = c(2.5, .5))
On peut voir que cette méthode a sélectionné des mots-clés importants en français liés au poste de data scientist ! Dans les premières positions, on trouve les mots-clés : “intelligence artificielle”, “tableaux de bord”, “enseignement supérieur”, “modèle prédictif”. Il vaut mieux vérifier si ces mots apparaissent sur mon CV !
Conclusion
J’espère vous avoir convaincu qu’il est possible d’optimiser votre recherche d’emploi avec la Data Science !
Si cet article vous a intéressé et que vous êtes à la recherche d’un nouveau Data Scientist, n’hésitez pas à me contacter par mail car je suis actuellement en recherche d’emploi en France (hybride, remote) ou en Europe (remote).
Fonctions personnalisées pour nettoyer les données extraites de la page web
Ces fonctions utilisent plusieurs méthodes telles que les expressions régulières, les mots vides et les instructions conditionnelles pour nettoyer les données textuelles.
library(rvest)
library(stringr)
library(httr)
library(tidystopwords)
library(textcat)
# Function to tidy the data related to the company
<- function(text){
tidy_comploc <- str_split(text, pattern = "\n", simplify =T)
lst <- substr(lst[1], nchar(lst[1])-2, nchar(lst[1]))
ext_str <- suppressWarnings(as.numeric(gsub(',', '.', ext_str)))
res 1] <- ifelse(is.na(res), lst[1], substr(lst[1], 1, nchar(lst[1])-3))
lst[3] <- res
lst[t(as.matrix(lst))
}
# Function to tidy the short job description provided with the job post
<- function(text){
tidy_job_desc <- c("Candidature facile", "Employeur réactif")
stopwords <- str_remove_all(text, paste(stopwords, collapse = "|"))
text <- "(Posted|Employer).*"
stopwords_2 <- str_remove_all(text, stopwords_2)
text
text
}
# Function to tidy the salary data if provided
<- function(text){
tidy_salary if(is.na(text)){
<- NA
others <- NA
sal_low <- NA
sal_high else{
}<- str_split(text, "\n", simplify = T)
text <- paste(text[str_detect(text, "€", negate = T)], collapse = " | ")
others <- text[str_detect(text, "€", negate = F)]
sal if(rlang::is_empty(sal)){
<- NA
sal_low <- NA
sal_high else{
}<- as.numeric(str_split(str_remove_all(str_replace(sal, "à", "-"), "[^0-9.-]"), "-", simplify = TRUE))
range_sal <- sort(range_sal)[1]
sal_low <- sort(range_sal)[2]
sal_high
if(str_detect(sal, "an")){
<- floor(sal_low/12)
sal_low <- floor(sal_high/12)
sal_high
}
}
}return(c(as.numeric(sal_low), as.numeric(sal_high), others))
}
# Function to tidy the location of the job (Remote/Hybrid/Onsite) + homogenize
# location and zip code
<- function(final_df){
tidy_location $Job_type <- ifelse(final_df$Location == "Télétravail", "Full Remote", ifelse(str_detect(final_df$Location, "Télétravail"), "Hybrid", "On site"))
final_df$Loc_possibility <- ifelse(str_detect(final_df$Location, "lieu"), "Plusieurs lieux", NA)
final_df<- c("Télétravail à", "Télétravail", "à", "hybride")
stopwords $Loc_tidy <- str_remove_all(final_df$Location, paste(stopwords, collapse = "|"))
final_df$Loc_tidy <- str_remove_all(final_df$Loc_tidy, "[+].*")
final_df$Loc_tidy <- str_trim(final_df$Loc_tidy)
final_df$Loc_tidy <- sapply(final_df$Loc_tidy,
final_dffunction(x){
if(!is.na(suppressWarnings(as.numeric(substr(x, 1, 5))))){
return(paste(substr(x, 7, 30), paste0('(', substr(final_df$Loc_tidy[2], 1, 2), ')')))
else{
}return(x)
}})return(final_df)
}
# Function to keep only certain words in text
<- function(text, keep) {
keep_words <- strsplit(text, " ")[[1]]
words <- paste(words[words %in% keep], collapse = " ")
txt return(txt)
}
# Homogenize the job title and class them in a few categories
<- function(job_titles){
clean_job_title <- tolower(job_titles)
job_titles <- gsub("[[:punct:]]", " ", job_titles, perl=TRUE)
job_titles
<- c("data", "scientist", "junior", "senior", "engineer", "nlp",
words_to_keep "analyst", "analytics", "analytic", "science", "sciences",
"computer", "vision", "ingenieur", "données", "analyste",
"analyses", "lead", "leader", "dataminer", "mining", "chief",
"miner", "analyse", 'head')
<- unlist(sapply(job_titles, function(x){keep_words(x, words_to_keep)}, USE.NAMES = F))
job_titles_c <- unlist(sapply(job_titles_c, function(x){paste(unique(unlist(str_split(x, " "))), collapse = " ")}, USE.NAMES = F))
job_titles_c table(job_titles_c)
<- job_titles_c %in% c("analyses data", "analyst data", "analyste data", "analyste data scientist", "data analyse",
data_analytics_ind "analyste données", "analytic data scientist", "analytics data", "analytics data engineer", "data analyst engineer",
"data analyst données", "data analyst scientist", "data analyst scientist données", "data analyste", "data analyst analytics",
"data analytics", "data analytics engineer", "data engineer analyst", "data scientist analyst", "data scientist analytics")
<- "data analyst"
job_titles_c[data_analytics_ind]
<- job_titles_c %in% c("junior data analyst", "junior data analytics", "junior data scientist analyst")
data_analytics_j_ind <- "data analyst junior"
job_titles_c[data_analytics_j_ind]
<- job_titles_c %in% c("data computer science", "data science", "data science scientist", "data sciences",
data_scientist_ind "data sciences scientist", "data scientist données", "data scientist sciences",
"données data scientist", "scientist data", "science données", "scientist data",
"scientist data science", "computer data science", "data science données", "data scientist science")
<- "data scientist"
job_titles_c[data_scientist_ind]
<- job_titles_c %in% c("junior data scientist")
data_scientist_j_ind <- "data scientist junior"
job_titles_c[data_scientist_j_ind]
<- job_titles_c %in% c("data engineer scientist", "data science engineer", "data miner", "data scientist engineer",
data_engineer_ind "dataminer", "engineer data scientist", "senior data scientist engineer", "ingenieur data scientist")
<- "data engineer"
job_titles_c[data_engineer_ind]
<- job_titles_c %in% c("data scientist nlp", "nlp data science",
nlp_data_scientist_ind "nlp data scientist", "senior data scientist nlp")
<- "data scientist NLP"
job_titles_c[nlp_data_scientist_ind]
<- job_titles_c %in% c("computer vision data scientist", "data science computer vision",
cv_data_scientist_ind "data scientist computer vision")
<- "data scientist CV"
job_titles_c[cv_data_scientist_ind]
<- job_titles_c %in% c("chief data", "chief data scientist", "data scientist leader", "lead data scientist",
lead_data_scientist_ind "data chief scientist", "lead data scientist senior", "head data science")
<- "data scientist lead or higher"
job_titles_c[lead_data_scientist_ind] <- job_titles_c %in% c("senior data scientist")
senior_data_scientist_ind <- "data scientist senior"
job_titles_c[senior_data_scientist_ind]
<- job_titles_c %in% c("senior analytics data scientist", "senior data analyst", "senior data scientist analytics")
senior_data_analytics_ind <- "data analyst senior"
job_titles_c[senior_data_analytics_ind]
<- job_titles_c %in% c("lead data analyst senior", "lead data analyst")
lead_data_analyst_ind <- "data analyst lead"
job_titles_c[lead_data_analyst_ind] return(job_titles_c)
}
# Function to clean the full job description before word annotation
<- function(text){
clean_job_desc <- tolower(text)
text <- str_replace_all(text, "\n", " ")
text <- str_remove(text, pattern = "dé.*du poste ")
text <- str_remove(text, pattern = "analyse de recr.*")
text <- gsub("(?!&)[[:punct:]+’+…+»+«]", " ", text, perl=TRUE)
text
<- textcat(text)
language
if(language == "french"){
<- str_replace_all(text, "œ", "oe")
text <- c("détails", "poste", "description", "informations", "complémentaires", "c", generate_stoplist(language = "French"))
stopwords else{
}<- c("description", generate_stoplist(language = "English"))
stopwords
}
<- str_replace_all(text, paste(stopwords, collapse = " | "), " ")
text <- str_replace_all(text, paste(stopwords, collapse = " | "), " ")
text <- str_replace_all(text, paste(stopwords, collapse = " | "), " ")
text
return(c(language, text))
}