Author: Paul Duan

Skip the run test because the ROME version has to be updated to make it work in the exported repository. TODO: Update ROME and remove the skiptest flag.

ROME Genderization

Problem statement: This notebook is an exploration of how we could normalize the genderization of (French) job titles in ROME (Répertoire Opérationnel des Métiers et des Emplois), which is published by Pôle Emploi. These are often inconsistently specified, which leads to confusion and poorer user experience. In addition it makes the job title longer than it should be, which makes them harder to identify at a glance.

Input: ROME job titles, with hardcoded genderization in the plaintext job titles.

Desired output: A more structured format where jobs have both a masculine and a feminine version (which may be the same if the job is not genderized).

Example problem and output: An English-language example of the problem would be that a job such as "Senior Fireman" might sometimes be genderized as "Senior Fireman / Senior Firewoman" and sometimes as "Senior Fire(wo)man". We want to turn any variant of these possible genderizations into a normalized pair of two fields: ("Senior Fireman", "Senior Firewoman"). If a job title is not gendered, for example "Artist", we want to simply return ("Artist", "Artist").

Additional outputs (TODO): We could also re-define a normalized way of returning a genderized string when the user's gender is unknown (like the current ROME job titles, but enforcing a consistent notation for how genderization is handled). This, along with the rest, could be given back to Pôle Emploi so we can help them push the improvements upstream to the official ROME.


In [1]:
from itertools import chain
import pandas as pd
import re

from bob_emploi.data_analysis.lib import cleaned_data

In [2]:
jobs = cleaned_data.rome_jobs('../../../data')

Exploratory analysis

First let's check out what the data look like and see if we can identify some patterns.


In [3]:
jobs


Out[3]:
name code_rome
code_ogr
10200 Abatteur / Abatteuse de carrière F1402
10201 Accastilleur-gréeur / Accastilleuse-gréeuse I1601
10202 Accastilleur(se)-monteur(se) électronique de m... I1601
10203 Accessoiriste L1503
10204 Accessoiriste de plateau L1503
10205 Accessoiriste en effets spéciaux L1503
10206 Accompagnateur / Accompagnatrice de randonnée G1201
10207 Accompagnateur / Accompagnatrice de randonnée ... G1201
10208 Accompagnateur / Accompagnatrice de séjour lin... G1201
10209 Accompagnateur / Accompagnatrice de tourisme é... G1201
10210 Accompagnateur / Accompagnatrice de tourisme s... G1201
10211 Accompagnateur / Accompagnatrice de voyages G1201
10212 Accompagnateur(trice) voyages activités cultur... G1201
10213 Accompagnateur / Accompagnatrice d'enfants ou ... G1203
10214 Accompagnateur / Accompagnatrice d'excursions G1201
10215 Accompagnateur / Accompagnatrice en écotourisme G1201
10216 Accompagnateur / Accompagnatrice en moyenne mo... G1201
10217 Accompagnateur / Accompagnatrice en tourisme vert G1201
10218 Accompagnateur(trice) formateur(trice) techniq... K1203
10219 Accompagnateur médicosocial / Accompagnatrice ... K1301
10220 Accompagnateur(trice) médicosocial(e) vie jour... K1301
10221 Accompagnateur(trice) médicosocial(e) de perso... K1301
10222 Accompagnateur(trice) médicosocial(e) personne... K1301
10224 Accompagnateur/Accompagnatrice reconversion pr... K1801
10225 Accompagnateur(trice) socioprofessionnel(le) K1203
10226 Accompagnateur(trice) technique d'insertion pr... K1203
10227 Accompagnateur / Accompagnatrice tourisme G1201
10228 Accompagnateur / Accompagnatrice touristique G1201
10229 Acconier / Acconière de l'armée N3203
10230 Accordeur / Accordeuse de pianos B1501
... ... ...
38930 Menuisier / Menuisière aluminium d'atelier en ... H2903
38931 BIM Modeleur / Modeleuse F1104
38932 BIM Manager F1106
38936 Gérant / Gérante en restauration collective G1404
38937 Cintreur / Cintreuse aluminium H2905
38939 Sondeur enquêteur / Sondeuse enquêtrice M1401
38956 Conseiller / Conseillère en crédit immobilier C1206
38957 Courtier / Courtière en prêts immobiliers C1206
38958 Assistant / Assistante service clients D1401
38959 Assistant / Assistante administration des vent... D1401
38960 Assistant / Assistante import-export D1401
38961 Assistant / Assistante commerce international D1401
38962 Chargé / Chargée de communication on line E1103
38963 Chargé / Chargée de communication web E1103
38964 Rédacteur / Rédactrice web E1104
38965 Responsable éditorial / éditoriale on line E1106
38966 Responsable éditorial / éditoriale web E1106
38967 Transcripteur(trice) adaptateur(trice) documen... E1108
38968 Juriste internet K1903
38969 Chef de projet e-formation K2111
38970 Data miner M1403
38971 Data analyst M1403
38972 Data scientist M1403
38973 Digital brand manager - Responsable de la marq... M1705
38974 Responsable marketing digital M1705
38975 Data manager M1802
38976 Développeur / Développeuse full-stack M1805
38977 Développeur / Développeuse Big Data M1805
38978 Consultant / Consultante IT M1806
38979 Conducteur / Conductrice de nacelle N1101

10929 rows × 2 columns

From a cursory look at the data we can see that there are at least two ways to genderize job titles:

  • masculine_title / feminine_title (e.g. "Abbateur / Abbateuse"); we'll call this the slash notation
  • masculine_title(feminine_postfix) (e.g."Accompagnateur(trice)"); we'll call this the bracket notation

When the adjective itself needs to be in concordance with the gender:

  • In the bracket notation, the adjective itself will have the gender postfix in a bracket, e.g. "Accompagnateur(trice) médicosocial(e) vie journalière" in jobs.loc['10220']
  • In the slash notation, both will be repeated, as in "Accompagnateur médicosocial / Accompagnatrice médicosociale" in jobs.loc['10219']

Remarks:

  • There isn't an obvious logic as to when bracket notation is used. One hypothesis I had was that this would depend on whether there would be an adjective to be concorded (in which case the bracket notation seems more adapted), but this is not the case. There are many examples of cases where bracket notation is still used despite there being no adjective (e.g. "Accompagnateur(trice) voyages" in jobs.loc['10212'], especially since later we have a "Accompagnateur / Accompagnatrice tourisme", as well as examples of items in slash notations that also have an adjective.
  • One tricky thing is that the postfix is sometimes meant to replace the word end as in "Accompagnateur(trice), whereas other times it's meant to be added to the end, as in "socioprofessionnel(le)"; this can be covered easily enough since there aren't too many possible postfixes, but this is one thing we have to be mindful of.
  • Another annoying thing is that in the slash notation, usually qualifiers are not repeated and are instead meant to be left-distributive; for example, "Accompagnateur / Accompagnatrice tourisme" should translate to ("Accompagnateur tourisme", "Accompagnatrice tourisme").
    • With that said, other times they are repeated, as in "Accompagnateur médicosocial / Accompagnatrice médicosociale", especially (but not necessarily) when the qualifier is a concorded adjective.
    • Though rarer, is sometimes also happens on the left side, for example with "Responsable éditorial / éditoriale web" in jobs.loc['38966'] which should translate to ("Responsable éditorial web", "Responsable éditoriale web") and features both a distributive qualifier on the left and on the right.

How many genderized job names are there?

As a quick way to estimate this number we'll just count the number of names containing slashes or brackets:


In [4]:
is_genderized = jobs['name'].apply(lambda x: '(' in x or '/' in x)

print('Number of genderized names :', sum(is_genderized))
print('Out of total names :', len(jobs), 'i.e.', sum(is_genderized)/len(jobs), '%')


Number of genderized names : 8642
Out of total names : 10929 i.e. 0.790740232409 %

Extracting gender

This is the part where we actually do things.

Bracket notation

  • Example: "Accompagnateur(trice) médicosocial(e) vie journalière"; we want ("Accompagnateur médicosocial vie journalière", "Accompagnatrice médicosociale vie journalière").

This is the more complex case. Here let's extract brackets and the preceding word (non-greedily, to account for cases where there are multiple brackets) but do not match when the character before the bracket is non-alphabetical. This is because we want to make sure the bracket is a postfix directly appended to a noun without space, since brackets can only be used by themselves.

As such, a possible regex expression that would capture both a genderized word and its postfix is:

r"(\S+?)\((\S+?)\)"

To only capture the bracket, a regex would be:

r"(?<=\S)\(([\S]+?)\)"

With the positive lookbehind ensuring that the preceding character is not a space.

How many types of postfixes are there?

Because rules for properly handling postfixes are complicated (and postfixes for according jobs and adjectives are few), I believe it's better to simply exhaustively list them then hardcode their associated substitution rules. Let's list them:

(side note: as a bonus this also verifies that our regex is good and has no false positives)


In [5]:
postfix_rule = re.compile(r"(?<=\S)\(([\S]+?)\)")

has_bracket = jobs['name'].apply(lambda x: '(' in x)
postfixes = jobs[has_bracket].name.apply(
    lambda x: re.findall(postfix_rule, x))

postfixes_types = set(chain.from_iterable(postfixes))
print(postfixes_types)


{'ne', 'e', 's', 'ère', 'se', 've', 'ive', 'le', 'sse', 'euse', 'trice', 're', 'rice', 'ière'}

Looks like our regex is pretty good!

Both nouns and qualifiers are subject to genderization. Here are some examples of how these postfixes are meant to transform the preceding word:

  • Abbateur(se) -> Abbateur, Abbateuse
  • Accompagnateur(trice) -> Accompagnateur, Accompagnatrice
  • social(e) -> social, sociale
  • Technicien(ne) -> Technicien, Technicienne
  • administratif(ive) -> administratif, administrative

Only the 's' postfix looks out of place, as it doesn't seem to be a gender postfix (it's a plural postfix). Let's see if this is a big deal:


In [6]:
jobs[jobs.name.apply(lambda x: '(s)' in x)]


Out[6]:
name code_rome
code_ogr
14330 Directeur / Directrice de site(s) d'entreposage N1302

Looks like there is only one case, and the '(s)' plural postfix is frankly unnecessary (here the singular form already implies you can supervise many sites). So instead of making the regex needlessly complex to account for this, we'll just skip this particular case in the substitution code.

Caveat: Is the mapping of postfixes to word endings bijective? Looking at the list of postfixes, it appears that we both have "se" and "euse" as possible postfixes. This is problematic, because it means the mapping is possibly inconsistent:


In [7]:
print(jobs[jobs.name.apply(lambda x: '(euse)' in x)][:3])
print(jobs[jobs.name.apply(lambda x: '(se)' in x)][:3])


                                                       name code_rome
code_ogr                                                             
13583     Contrôleur(euse) aérien(ne) de la circulation ...     N2202
38574     Manager(euse) commercial(e) junior des forces ...     D1406
38916     Développeur(euse) de sécurité des systèmes d'i...     M1805
                                                       name code_rome
code_ogr                                                             
10202     Accastilleur(se)-monteur(se) électronique de m...     I1601
10454     Agent(e) de fabrication-deviseur(se) en indust...     E1308
10858     Aide monteur(se) installations en télécom cour...     I1307

It appears that masculine words like "Accastilleur" and "Contrôleur", which both end in -eur, sometimes use the "(se)" and sometimes the "(euse)" postfix!

We also see that conversely, (euse) is both used for words ending in -er (like "Manager") and -eur ("Contrôleur")! Which means the postfixes are not normalized and we unfortunately have a many-to-many mapping :(

Slash notation

  • Example: "Accordeur / Accordeuse de pianos"; we want ("Accordeur de pianos", "Accordeuse de pianos").

This case is easier: we'll just split the sentence according to slashes. The left side of the slash is always the masculine case, and the right side the feminine case.

Caveat: We need to make sure we handle qualifiers properly, because sometimes they are repeated on both sides of the slash and sometimes not, in which case they are meant to be distributive. For example, in "Responsable éditorial / éditoriale web", the word "Responsable" on the left side is meant to be repeated on the right side, and conversely the word "web" on the right side must be repeated on the left side. Sometimes this is not the case (all qualifiers are already repeated on both sides), sometimes one side only features distributive qualifiers, sometimes both (as in the example in the previous sentence).

One way of handling this is to first insert any words at the beginning of the left side that is not present in the ride side in front of the string in the right side.

Then once this is done we can consider that if the left side contains n words, then these also represent the first n words on the right side, with all extra words on the ride side needing to be appended to the left side as well.

Putting it all back together


In [8]:
POSTFIX_MAP = {
    'e': [''],  # empty string if the postfix can be appended
    'ère': ['er'],
    'se': ['r'],
    'sse': [''],
    'euse': ['eur', 'er'],
    've': ['f'],
    're': ['r'],
    'rice': ['eur'],
    'ne': [''],
    'trice': ['teur'],
    'le': [''],
    'ive': ['if'],
    'ière': ['ier'],
}


def check_mapping_specification(postfix_map):
    """Check whether the mapping of postfix to word endings
    correctly returns a list of possible word endings to substitute
    that goes from more specified (longer) to more general."""

    for postfix, postfix_map in postfix_map.items():
        if postfix_map != sorted(postfix_map, key=len, reverse=True):
            return False
    return True

def substitute_postfix(word, postfix, postfix_map):
    """Perform the correct postfix substitution from a
    masculine word to a feminine word, taking into account the
    fact that some postfix are meant to be appended to
    the base string while others are meant to be substituted.
    Both nouns and adjectives may be genderized.
    Examples:
    - Abbateur(se) -> Abbateur, Abbateuse
    - Accompagnateur(trice) -> Accompagnateur, Accompagnatrice
    - social(e) -> social, sociale
    - Technicien(ne) -> Technicien, Technicienne
    - administratif(ive) -> administratif, administrative
    etc. The integral set of postfixes in the ROME dataset is:
    {'e', 'ère', 'se', 'sse', 'euse', 've', 're',
     'rice', 'ne', 'trice', 'le', 's', 'ive', 'ière'}
    (The 's' postfix is an exception, as it relates to
    the plural form rather than gender.)

    Since there are only a limited number of postfixes, we can
    exhaustively list them and the word ending they are meant to
    substitute, which is safer in case new unaccounted ones pop up.
    
    Note that the same postfix can be specified multiple ways
    for multiple types of word endings.
    For example, "Manager(euse)" is feminized as "Manageuse", but
    both "Masseur(euse)" and "Masseur(se)" both translate to "Masseuse".
    
    Therefore the substitution dictionary has the form:
    {postfix: [possible_word_ending1, possible_word_ending2, ...]}
    Here we perform the first substitution that matches the actual
    word ending. This means the list of possible word endings must
    therefore be ordered by decreasing length, so more specific cases
    are always checked before more general ones.
    """
    if not len(word):
        raise ValueError("word is empty")
    
    if postfix in POSTFIX_MAP:
        known_endings = POSTFIX_MAP[postfix]
        
        for known_ending in known_endings:
            if word.endswith(known_ending):    
                root = word[:len(word) - len(known_ending)]
                return root + postfix

        error_string = "{0}: unmapped word ending for postfix '{1}'"
        raise ValueError(error_string.format(word, postfix))
    else:
        raise ValueError("unknown postfix:" + postfix)

def extract_bracket_notation(raw_job_name, postfix_map):
    """Extract the genderized strings from a job title
    with the bracket notation, by going through the string
    and replacing brackets.
    
    Return None if the item doesn't appear to be in
    bracket notation."""
    # nb: we only want brackets directly following a character
    bracket_regex = re.compile(r"(\S+?)\((\S+?)\)")

    matches = re.findall(bracket_regex, raw_job_name)
    if not matches:
        return None
    
    # masculine name is just the string without the bracket content;
    # to get feminine names we also substitute the relevant words.
    # we'll perform these deletions/substitutions iteratively
    masculine_name = feminine_name = raw_job_name
    for word, postfix in matches:
        if postfix != 's':  # if not the plural postfix edge case
            masculine_name = masculine_name.replace("({})".format(postfix), '')
            feminine_name = feminine_name.replace("({})".format(postfix), '')

            new_word = substitute_postfix(word, postfix, postfix_map)
            feminine_name = feminine_name.replace(word, new_word)
    
    return masculine_name, feminine_name

In [9]:
def extract_slash_notation(raw_job_name):
    """Extract the genderized strings from a job title
    with the slash notation. Simply split the raw job
    name according to the slashes, then deal with the
    qualifiers distribution by appending all additional
    words from the feminine (right) string to the masculine one.
    
    In addition, any leading words on the left side must be
    appended to the right side as well. One subtlety is that
    detecting when the leading words end (or if they exist) is
    complicated by the fact that the first common word may be
    gendered, so we need to fuzzy match. For our purposes simply
    comparing the first few characters should be enough.
    
    Return None if the item doesn't appear to be in
    slash notation."""
    chars_to_compare = 3
    substrings = raw_job_name.split(' / ')      
    
    if len(substrings) != 2:
        return None
    
    masculine_name, feminine_name = substrings
    
    feminine_words = feminine_name.split(' ')
    masculine_words = masculine_name.split(' ')
    n_masculine_words = len(masculine_words)
    
    # insert until a word that looks like right side's first
    # word is encountered
    to_insert = []
    for word in masculine_words:
        if word[:chars_to_compare] != feminine_words[0][:chars_to_compare]:
            to_insert.append(word)
        else:
            break
    feminine_words = to_insert + feminine_words
    feminine_name = ' '.join(feminine_words)

    # append extra right-side words to the the left side
    for i, word in enumerate(feminine_words):
        if i > n_masculine_words - 1:
            masculine_words.append(word)
    masculine_name = ' '.join(masculine_words)
    
    return masculine_name, feminine_name

In [10]:
def genderize(df, postfix_map):
    """Take a dataframe of the same form as the
    one returned by cleaned_data.rome_jobs and genderize it,
    adding a masculine_name and a feminine_name field to it.
    
    By default, masculine_name = feminine_name = raw_job_name,
    then we overwrite the value when either the slash notation
    or bracket notation rule is successful."""
    
    masculine_name = df['name'].copy()
    feminine_name = df['name'].copy()
    
    bracket_output = df.name.apply(extract_bracket_notation,
                                   postfix_map=postfix_map)
    slash_output = df.name.apply(extract_slash_notation)
    
    is_bracket = bracket_output.notnull()
    is_slash = slash_output.notnull()
    
    masculine_name[is_bracket] = bracket_output[is_bracket].apply(
        lambda x: x[0])
    feminine_name[is_bracket] = bracket_output[is_bracket].apply(
        lambda x: x[1])
    
    masculine_name[is_slash] = slash_output[is_slash].apply(
        lambda x: x[0])
    feminine_name[is_slash] = slash_output[is_slash].apply(
        lambda x: x[1])
    
    df['masculine_name'] = masculine_name
    df['feminine_name'] = feminine_name
    
    return df

Now all that's left is to run it:


In [11]:
postfix_map = POSTFIX_MAP
assert check_mapping_specification(postfix_map), "ill-specified postfix map"

genderize(jobs, postfix_map)


Out[11]:
name code_rome masculine_name feminine_name
code_ogr
10200 Abatteur / Abatteuse de carrière F1402 Abatteur de carrière Abatteuse de carrière
10201 Accastilleur-gréeur / Accastilleuse-gréeuse I1601 Accastilleur-gréeur Accastilleuse-gréeuse
10202 Accastilleur(se)-monteur(se) électronique de m... I1601 Accastilleur-monteur électronique de marine Accastilleuse-monteuse électronique de marine
10203 Accessoiriste L1503 Accessoiriste Accessoiriste
10204 Accessoiriste de plateau L1503 Accessoiriste de plateau Accessoiriste de plateau
10205 Accessoiriste en effets spéciaux L1503 Accessoiriste en effets spéciaux Accessoiriste en effets spéciaux
10206 Accompagnateur / Accompagnatrice de randonnée G1201 Accompagnateur de randonnée Accompagnatrice de randonnée
10207 Accompagnateur / Accompagnatrice de randonnée ... G1201 Accompagnateur de randonnée nature Accompagnatrice de randonnée nature
10208 Accompagnateur / Accompagnatrice de séjour lin... G1201 Accompagnateur de séjour linguistique Accompagnatrice de séjour linguistique
10209 Accompagnateur / Accompagnatrice de tourisme é... G1201 Accompagnateur de tourisme équestre Accompagnatrice de tourisme équestre
10210 Accompagnateur / Accompagnatrice de tourisme s... G1201 Accompagnateur de tourisme sportif Accompagnatrice de tourisme sportif
10211 Accompagnateur / Accompagnatrice de voyages G1201 Accompagnateur de voyages Accompagnatrice de voyages
10212 Accompagnateur(trice) voyages activités cultur... G1201 Accompagnateur voyages activités culturelles s... Accompagnatrice voyages activités culturelles ...
10213 Accompagnateur / Accompagnatrice d'enfants ou ... G1203 Accompagnateur d'enfants ou d'adolescents Accompagnatrice d'enfants ou d'adolescents
10214 Accompagnateur / Accompagnatrice d'excursions G1201 Accompagnateur d'excursions Accompagnatrice d'excursions
10215 Accompagnateur / Accompagnatrice en écotourisme G1201 Accompagnateur en écotourisme Accompagnatrice en écotourisme
10216 Accompagnateur / Accompagnatrice en moyenne mo... G1201 Accompagnateur en moyenne montagne Accompagnatrice en moyenne montagne
10217 Accompagnateur / Accompagnatrice en tourisme vert G1201 Accompagnateur en tourisme vert Accompagnatrice en tourisme vert
10218 Accompagnateur(trice) formateur(trice) techniq... K1203 Accompagnateur formateur technique insert prof Accompagnatrice formatrice technique insert prof
10219 Accompagnateur médicosocial / Accompagnatrice ... K1301 Accompagnateur médicosocial Accompagnatrice médicosociale
10220 Accompagnateur(trice) médicosocial(e) vie jour... K1301 Accompagnateur médicosocial vie journalière Accompagnatrice médicosociale vie journalière
10221 Accompagnateur(trice) médicosocial(e) de perso... K1301 Accompagnateur médicosocial de personne dépend... Accompagnatrice médicosociale de personne dépe...
10222 Accompagnateur(trice) médicosocial(e) personne... K1301 Accompagnateur médicosocial personnes en post-... Accompagnatrice médicosociale personnes en pos...
10224 Accompagnateur/Accompagnatrice reconversion pr... K1801 Accompagnateur/Accompagnatrice reconversion pr... Accompagnateur/Accompagnatrice reconversion pr...
10225 Accompagnateur(trice) socioprofessionnel(le) K1203 Accompagnateur socioprofessionnel Accompagnatrice socioprofessionnelle
10226 Accompagnateur(trice) technique d'insertion pr... K1203 Accompagnateur technique d'insertion professio... Accompagnatrice technique d'insertion professi...
10227 Accompagnateur / Accompagnatrice tourisme G1201 Accompagnateur tourisme Accompagnatrice tourisme
10228 Accompagnateur / Accompagnatrice touristique G1201 Accompagnateur touristique Accompagnatrice touristique
10229 Acconier / Acconière de l'armée N3203 Acconier de l'armée Acconière de l'armée
10230 Accordeur / Accordeuse de pianos B1501 Accordeur de pianos Accordeuse de pianos
... ... ... ... ...
38930 Menuisier / Menuisière aluminium d'atelier en ... H2903 Menuisier aluminium d'atelier en industrie Menuisière aluminium d'atelier en industrie
38931 BIM Modeleur / Modeleuse F1104 BIM Modeleur BIM Modeleuse
38932 BIM Manager F1106 BIM Manager BIM Manager
38936 Gérant / Gérante en restauration collective G1404 Gérant en restauration collective Gérante en restauration collective
38937 Cintreur / Cintreuse aluminium H2905 Cintreur aluminium Cintreuse aluminium
38939 Sondeur enquêteur / Sondeuse enquêtrice M1401 Sondeur enquêteur Sondeuse enquêtrice
38956 Conseiller / Conseillère en crédit immobilier C1206 Conseiller en crédit immobilier Conseillère en crédit immobilier
38957 Courtier / Courtière en prêts immobiliers C1206 Courtier en prêts immobiliers Courtière en prêts immobiliers
38958 Assistant / Assistante service clients D1401 Assistant service clients Assistante service clients
38959 Assistant / Assistante administration des vent... D1401 Assistant administration des ventes export Assistante administration des ventes export
38960 Assistant / Assistante import-export D1401 Assistant import-export Assistante import-export
38961 Assistant / Assistante commerce international D1401 Assistant commerce international Assistante commerce international
38962 Chargé / Chargée de communication on line E1103 Chargé de communication on line Chargée de communication on line
38963 Chargé / Chargée de communication web E1103 Chargé de communication web Chargée de communication web
38964 Rédacteur / Rédactrice web E1104 Rédacteur web Rédactrice web
38965 Responsable éditorial / éditoriale on line E1106 Responsable éditorial on line Responsable éditoriale on line
38966 Responsable éditorial / éditoriale web E1106 Responsable éditorial web Responsable éditoriale web
38967 Transcripteur(trice) adaptateur(trice) documen... E1108 Transcripteur adaptateur documents spécialisés Transcriptrice adaptatrice documents spécialisés
38968 Juriste internet K1903 Juriste internet Juriste internet
38969 Chef de projet e-formation K2111 Chef de projet e-formation Chef de projet e-formation
38970 Data miner M1403 Data miner Data miner
38971 Data analyst M1403 Data analyst Data analyst
38972 Data scientist M1403 Data scientist Data scientist
38973 Digital brand manager - Responsable de la marq... M1705 Digital brand manager - Responsable de la marq... Digital brand manager - Responsable de la marq...
38974 Responsable marketing digital M1705 Responsable marketing digital Responsable marketing digital
38975 Data manager M1802 Data manager Data manager
38976 Développeur / Développeuse full-stack M1805 Développeur full-stack Développeuse full-stack
38977 Développeur / Développeuse Big Data M1805 Développeur Big Data Développeuse Big Data
38978 Consultant / Consultante IT M1806 Consultant IT Consultante IT
38979 Conducteur / Conductrice de nacelle N1101 Conducteur de nacelle Conductrice de nacelle

10929 rows × 4 columns

Voilà !