Exercices pour atelier : R pour la linguistique

Published

November 23, 2025

# keep
knitr::opts_chunk$set(warning = FALSE, message = FALSE) 

2. Configuration

Vous devez exécuter le code ci-dessous pour vérifier, puis si nécessaire télécharger et charger, les packages nécessaires aux exercices.

# keep
source("funs.R")

verifier_packages(c("tidyverse", "glmnet", "tuneR", "phonTools", "umap", "dtw", "udpipe", "readtextgrid", "stringi"))

Le code ci-dessous configure les paramètres nécessaires à notre travail.

# keep
options(dplyr.summarise.inform = FALSE)
theme_set(theme_minimal())
Sys.setenv(LANG = "fr")

3. Données tabulaires

Dans le «bloc» de code ci-dessous, télécharchez des données donnees/peterson_barney_voyelle_eng.csv sauvegardez le tibble dans l’objet phone et regardez les données. Comparez ce tableau avec le tableau figurant dans les notes.

phone <- read_csv2("donnees/peterson_barney_voyelle_eng.csv")
phone
# A tibble: 1,520 × 9
      id groupe genre    api   xsampa    f0    f1    f2    f3
   <dbl> <chr>  <chr>    <chr> <chr>  <dbl> <dbl> <dbl> <dbl>
 1     1 homme  masculin i     i        160   240  2280  2850
 2     1 homme  masculin i     i        186   280  2400  2790
 3     1 homme  masculin ɪ     I        203   390  2030  2640
 4     1 homme  masculin ɪ     I        192   310  1980  2550
 5     1 homme  masculin ɛ     E        161   490  1870  2420
 6     1 homme  masculin ɛ     E        155   570  1700  2600
 7     1 homme  masculin æ     {        140   560  1820  2660
 8     1 homme  masculin æ     {        180   630  1700  2550
 9     1 homme  masculin ʌ     V        144   590  1250  2620
10     1 homme  masculin ʌ     V        148   620  1300  2530
# ℹ 1,510 more rows

4. Visualiser

Créez une visualisation de tibble phone de F2 et F1 dans la même forme que dans les notes, mais appliquez les couleur pour indiquer la groupe. Si vouz avez des problèmes avec les symboles sur votre ordinateur, utilisez la colonne xsampa au lieu d’API.

phone |>
  ggplot() +
  geom_text(aes(x=f2, y=f1, label=api, colour=groupe)) +
  scale_x_reverse() +
  scale_y_reverse()

Creez un « boites à moustaches » (boxplot en anglais) pour les valeurs F1 selon les symboles API avec la géométrie geom_boxplot. Donnez simplement des esthétiques x et y.

phone |>
  ggplot() +
  geom_boxplot(aes(x=api, y=f1))

Posez vos questions si vous ne comprenez pas la signification de chaque partie d’un «boxplot» !

Créez une visualization d’un «boxplot» comme ci-dessus, mais cette fois ajoutez une esthétique de la couleur associée à la colonne groupe. Faites attention aux résultats.

phone |>
  ggplot() +
  geom_boxplot(aes(x=api, y=f1, colour=groupe))

Copiez le code ci-dessus, puis remplacez l’estétique «colour» par «fill».

phone |>
  ggplot() +
  geom_boxplot(aes(x=api, y=f1, fill=groupe))

C’est quoi la différence entre «colour» et «fill» dans la grammaire des graphiques?

5. Manipuler un tableau

Sélectionnez les 5 lignes ayant les valeurs F1 les plus élevées.

phone |>
  arrange(f1) |>
  slice_head(n=5)
# A tibble: 5 × 9
     id groupe genre    api   xsampa    f0    f1    f2    f3
  <dbl> <chr>  <chr>    <chr> <chr>  <dbl> <dbl> <dbl> <dbl>
1    19 homme  masculin i     i        129   190  2650  3280
2    19 homme  masculin i     i        135   190  2700  3170
3     8 homme  masculin ɪ     I        103   206  2130  2570
4     2 homme  masculin i     i        148   210  2360  3250
5    18 homme  masculin i     i        124   210  2100  3090

Sélectionnez les 5 lignes ayant les valeurs F1 les plus basses.

phone |>
  arrange(desc(f1)) |>
  slice_head(n=5)
# A tibble: 5 × 9
     id groupe genre    api   xsampa    f0    f1    f2    f3
  <dbl> <chr>  <chr>    <chr> <chr>  <dbl> <dbl> <dbl> <dbl>
1    62 enfant <NA>     ɑ     A        200  1300  1800  3450
2    74 enfant masculin æ     {        260  1300  2280  3130
3    74 enfant masculin æ     {        260  1300  2160  3300
4    67 enfant <NA>     æ     {        214  1240  2700  3640
5    74 enfant masculin ɑ     A        250  1230  1300  3200

Selectionez les lignes qui ne sont pas associées aux hommes en appliquons le symbole != au lieu de ==.

phone |>
  filter(groupe != "homme")
# A tibble: 860 × 9
      id groupe genre api   xsampa    f0    f1    f2    f3
   <dbl> <chr>  <chr> <chr> <chr>  <dbl> <dbl> <dbl> <dbl>
 1    34 femme  <NA>  i     i        230   370  2670  3100
 2    34 femme  <NA>  i     i        234   390  2760  3060
 3    34 femme  <NA>  ɪ     I        234   468  2330  2930
 4    34 femme  <NA>  ɪ     I        205   410  2380  2950
 5    34 femme  <NA>  ɛ     E        190   550  2200  2880
 6    34 femme  <NA>  ɛ     E        191   570  2100  3040
 7    34 femme  <NA>  æ     {        200   800  1980  2810
 8    34 femme  <NA>  æ     {        192   860  1920  2850
 9    34 femme  <NA>  ʌ     V        227   635  1200  3250
10    34 femme  <NA>  ʌ     V        200   700  1200  3100
# ℹ 850 more rows

Créez de nouvelles variables qui donnent la différence entre F1 et F0 et entre F2 et F1. Elles sont parfois utiles pour comprendre les qualités perçues des voyelles.

phone |>
  filter(groupe != "homme")
# A tibble: 860 × 9
      id groupe genre api   xsampa    f0    f1    f2    f3
   <dbl> <chr>  <chr> <chr> <chr>  <dbl> <dbl> <dbl> <dbl>
 1    34 femme  <NA>  i     i        230   370  2670  3100
 2    34 femme  <NA>  i     i        234   390  2760  3060
 3    34 femme  <NA>  ɪ     I        234   468  2330  2930
 4    34 femme  <NA>  ɪ     I        205   410  2380  2950
 5    34 femme  <NA>  ɛ     E        190   550  2200  2880
 6    34 femme  <NA>  ɛ     E        191   570  2100  3040
 7    34 femme  <NA>  æ     {        200   800  1980  2810
 8    34 femme  <NA>  æ     {        192   860  1920  2850
 9    34 femme  <NA>  ʌ     V        227   635  1200  3250
10    34 femme  <NA>  ʌ     V        200   700  1200  3100
# ℹ 850 more rows

quantile est une autre fonction qu’on applique souvent dans la fonction summarise. On donne un argument probs, qui est un nombre entre 0 et 1. Le résultat est la valeur pour laquelle probs pour cent des données sont inférieures et 1-probs pour cent des données sont supérieures. Par example, quantile(f1, probs=0.5) donne la même chose que la fonction median(). Dans le code ci-dessous, créez un tableau avec une colonne f1_q1 qui donne le quantile avec probabilité 0.25 selon chaque symbole API.

phone |>
  group_by(api) |>
  summarise(
    f1_q1 = quantile(f1, probs=0.25)
  )
# A tibble: 10 × 2
   api   f1_q1
   <chr> <dbl>
 1 i      260 
 2 u      310.
 3 æ      668.
 4 ɑ      724.
 5 ɔ      540 
 6 ɛ      525 
 7 ɜ˞     460 
 8 ɪ      398.
 9 ʊ      430 
10 ʌ      630 

Continuez de créer les colonnes f1_q1, f1_q2, f1_q3, f2_q1, f2_q2, f2_q3 avec les probs 0.25, 0.50 et 0.75. Sauvgardez les résultats dans un nouveau object phone_quant.

phone_quant <- phone |>
  group_by(api) |>
  summarise(
    f1_q1 = quantile(f1, probs=0.25),
    f1_q2 = quantile(f1, probs=0.50),
    f1_q3 = quantile(f1, probs=0.75),
    f2_q1 = quantile(f2, probs=0.25),
    f2_q2 = quantile(f2, probs=0.50),
    f2_q3 = quantile(f2, probs=0.75)
  )
phone_quant
# A tibble: 10 × 7
   api   f1_q1 f1_q2 f1_q3 f2_q1 f2_q2 f2_q3
   <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 i      260   300   337. 2305  2700  2872.
 2 u      310.  350   400   808.  952. 1092.
 3 æ      668.  772.  940  1720  1935  2160 
 4 ɑ      724.  800   936. 1092. 1178. 1292.
 5 ɔ      540   584   660   820.  900  1000 
 6 ɛ      525   580.  640  1870  2200  2440 
 7 ɜ˞     460   506   544  1350  1515  1706.
 8 ɪ      398.  427   475. 2000  2308. 2584.
 9 ʊ      430   462   500  1000  1120  1274.
10 ʌ      630   705   790. 1200  1335  1500 

Pour aller plus loin Il y a une géométrie geom_segment qui a quatre esthétiques obligatoires: x, xend, y et yend. Cette géométrie crée des segments de ligne. Créez une visualisation des valeurs F1 et F2 en utilisant les quantiles.

phone_quant |>
  ggplot() +
  geom_segment(aes(x=f2_q2, xend=f2_q2, y=f1_q1, yend=f1_q3)) +
  geom_segment(aes(x=f2_q1, xend=f2_q3, y=f1_q2, yend=f1_q2)) +
  geom_text(aes(x=f2_q2 - 50, y=f1_q2 - 25, label=api)) +
  scale_x_reverse() +
  scale_y_reverse()

6. Les modèles statistiques

Télécharger les données avec le code ci-dessous.

# keep
mots <- read_csv2("donnees/keylog-mots.csv.bz2")
mots
# A tibble: 210,337 × 6
   id                mot    char_mot dur_mot    d1    d2
   <chr>             <chr>     <dbl>   <dbl> <dbl> <dbl>
 1 R_00RbUqO7jXLDItP If            2   312.   928.  848.
 2 R_00RbUqO7jXLDItP I             1    80.2 1600. 1520 
 3 R_00RbUqO7jXLDItP could         5  1576.  2168. 2088.
 4 R_00RbUqO7jXLDItP choose        6   945.   976.  904.
 5 R_00RbUqO7jXLDItP to            2   200.   930.  849.
 6 R_00RbUqO7jXLDItP be            2   151    528.  456.
 7 R_00RbUqO7jXLDItP any           3   440.   264   168.
 8 R_00RbUqO7jXLDItP animal        6  1560    976.  888 
 9 R_00RbUqO7jXLDItP for           3   264.   248   176.
10 R_00RbUqO7jXLDItP one           3   312   1120. 1048.
# ℹ 210,327 more rows

Creéz un object mots_sub de mot qui ne contient que les mots “the” et “and”.

mots_sub <- mots |>
  filter(mot %in% c("the", "and"))

Faites un «test de Student» qui teste la différence de durée entre taper “the” et taper “and”.

t.test(dur_mot ~ mot, data = mots_sub)

    Welch Two Sample t-test

data:  dur_mot by mot
t = 5.737, df = 11711, p-value = 9.875e-09
alternative hypothesis: true difference in means between group and and group the is not equal to 0
95 percent confidence interval:
 30.80760 62.78572
sample estimates:
mean in group and mean in group the 
         399.9284          353.1317 

Faites un «régression linéaire» qui prédit la durée d’un mot en fonction de sa longueur en caractères.

summary(lm(dur_mot ~ char_mot, data = mots))

Call:
lm(formula = dur_mot ~ char_mot, data = mots)

Residuals:
    Min      1Q  Median      3Q     Max 
 -11151    -851    -236     268 2575323 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1154.388     34.892  -33.09   <2e-16 ***
char_mot      557.574      6.727   82.89   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 8453 on 210335 degrees of freedom
Multiple R-squared:  0.03163,   Adjusted R-squared:  0.03162 
F-statistic:  6870 on 1 and 210335 DF,  p-value: < 2.2e-16

Interprétez les résultats. Sont-ils statistiquement significatifs ?

7. Plusieurs tableaux

Télécharger les données avec le code ci-dessous.

meta <- read_csv2("donnees/keylog-meta.csv.bz2")
meta
# A tibble: 823 × 4
   id                  age lang    cefr 
   <chr>             <dbl> <chr>   <chr>
 1 R_2EGIsZARLydD3Uc    25 Italian C1/C2
 2 R_1obCaysaZCWZXoG    22 Spanish B1/B2
 3 R_3fqTek829k38iCk    22 Polish  B1/B2
 4 R_brxD7Q5ZnPW8Gn7    43 English C1/C2
 5 R_1k1RE78cBbZyZMA    23 Polish  B1/B2
 6 R_1NwuZMzRkVIR0WT    32 English C1/C2
 7 R_2t8LOS9nQDBQPA8    24 Spanish C1/C2
 8 R_239Q0X5YLwB7U6Z    28 English C1/C2
 9 R_10xbkjEmnsusfb1    32 Polish  B1/B2
10 R_10CbLBzAnYKgWxB    21 Polish  B1/B2
# ℹ 813 more rows

Télécharger un autre tableau avec :

langs <- read_csv2("donnees/langs.csv")
langs
# A tibble: 8 × 2
  lang       groupe    
  <chr>      <chr>     
1 Polish     slaves    
2 English    germanique
3 German     germanique
4 Greek      hellénique
5 French     romane    
6 Italian    romane    
7 Portuguese romane    
8 Spanish    romane    

Combinez les données meta et langs à l’aide de la fonction left_join.

meta |>
  left_join(langs, by = "lang")
# A tibble: 823 × 5
   id                  age lang    cefr  groupe    
   <chr>             <dbl> <chr>   <chr> <chr>     
 1 R_2EGIsZARLydD3Uc    25 Italian C1/C2 romane    
 2 R_1obCaysaZCWZXoG    22 Spanish B1/B2 romane    
 3 R_3fqTek829k38iCk    22 Polish  B1/B2 slaves    
 4 R_brxD7Q5ZnPW8Gn7    43 English C1/C2 germanique
 5 R_1k1RE78cBbZyZMA    23 Polish  B1/B2 slaves    
 6 R_1NwuZMzRkVIR0WT    32 English C1/C2 germanique
 7 R_2t8LOS9nQDBQPA8    24 Spanish C1/C2 romane    
 8 R_239Q0X5YLwB7U6Z    28 English C1/C2 germanique
 9 R_10xbkjEmnsusfb1    32 Polish  B1/B2 slaves    
10 R_10CbLBzAnYKgWxB    21 Polish  B1/B2 slaves    
# ℹ 813 more rows

Comme décrit dans les notes, combinez mots et meta. Puis, combinez avec langs et calculez la médiane de d1. Sortez les lignes par les médianes.

mots |>
  left_join(meta, by = "id") |>
  left_join(langs, by = "lang") |>
  group_by(groupe) |>
  summarise(mu = median(d1)) |>
  arrange(desc(mu))
# A tibble: 4 × 2
  groupe        mu
  <chr>      <dbl>
1 hellénique  608.
2 slaves      520 
3 romane      496.
4 germanique  436 

8. Fenêtres glissantes

Télécharger les données avec le code ci-dessous.

touches <- read_csv2("donnees/keylog-touches.csv.bz2", na="NA")
touches
# A tibble: 1,145,051 × 7
   id                    t0     t1   dur dur_apres touche code 
   <chr>              <dbl>  <dbl> <dbl>     <dbl> <chr>  <chr>
 1 R_00RbUqO7jXLDItP 20914. 20978.  64.4      80.1 "I"    KeyI 
 2 R_00RbUqO7jXLDItP 21146. 21226.  80.1      55.8 "f"    KeyF 
 3 R_00RbUqO7jXLDItP 21234. 21290.  55.8      80.2 ""     Space
 4 R_00RbUqO7jXLDItP 22074. 22154.  80.2      88.2 "I"    KeyI 
 5 R_00RbUqO7jXLDItP 22306. 22394.  88.2      64.3 ""     Space
 6 R_00RbUqO7jXLDItP 23674. 23739.  64.3      56.1 "c"    KeyC 
 7 R_00RbUqO7jXLDItP 23818. 23874.  56.1      46.6 "o"    KeyO 
 8 R_00RbUqO7jXLDItP 24044. 24090.  46.6      64   "u"    KeyU 
 9 R_00RbUqO7jXLDItP 25066. 25130.  64        79.8 "l"    KeyL 
10 R_00RbUqO7jXLDItP 25170. 25250   79.8      72.3 "d"    KeyD 
# ℹ 1,145,041 more rows

Utilisez la fonction lead de creer un nouvelle colonne space_apres qui indique si le prochain touche et un espace. Puis, utilisez filter pour sélectionner les lignes qui ne sont pas des espaces elle-mêmes. Calculez la durée médiane d’une frappe selon la présence d’un espace après celui-ci.

touches |>
  mutate(space_apres = lead(code) == "Space") |>
  filter(code != "Space") |>
  filter(!is.na(space_apres)) |>
  group_by(space_apres) |>
  summarise(
    dur_moyenne = median(dur_apres)
  )
# A tibble: 2 × 2
  space_apres dur_moyenne
  <lgl>             <dbl>
1 FALSE              75.1
2 TRUE               79  

9. Chaînes de caractères

Télécharger les données avec le code ci-dessous.

noms <- read_csv2("donnees/lexique_noms.csv")
noms
# A tibble: 27,318 × 3
   lemme genre  freq
   <chr> <chr> <dbl>
 1 homme m     1399.
 2 jour  m     1342.
 3 temps m     1289.
 4 oeil  m     1235.
 5 fois  f     1140 
 6 chose f     1058.
 7 peu   m     1023.
 8 femme f      996.
 9 heure f      924.
10 tête  f      923.
# ℹ 27,308 more rows

Utilisez la fonction stri_extract pour créer une nouvelle colonne ter qui a le dernier caractère de chaque mot. Calculez la proportion de noms féminins pour chaque lettre terminale, filtrez en fonction des lettres comportant au moins 10 mots et classez les résultats par ordre décroissant de la proportion de noms féminins.

noms |>
  mutate(ter = stri_extract(lemme, regex="\\w\\Z")) |>
  group_by(ter) |>
  summarise(
    prop_f = mean(genre == "f"),
    n = n()
  ) |>
  filter(n > 10) |>
  arrange(desc(prop_f))
# A tibble: 24 × 3
   ter   prop_f     n
   <chr>  <dbl> <int>
 1 é     0.675   1233
 2 e     0.625  11635
 3 n     0.529   3507
 4 a     0.399    637
 5 w     0.182     11
 6 p     0.152     99
 7 y     0.149    121
 8 x     0.122    229
 9 s     0.0944  1102
10 b     0.0943    53
# ℹ 14 more rows

10. TextGrid

Télécharger un fichier de rhapsodie avec le code ci-dessous.

# keep
tg <- read_textgrid(
  "donnees/rhapsodie/tg/Rhap-D0001-Pro.TextGrid",
  encoding="UTF-8"
)
tg
# A tibble: 10,546 × 10
   file       tier_num tier_name tier_type tier_xmin tier_xmax  xmin  xmax text 
   <chr>         <int> <chr>     <chr>         <dbl>     <dbl> <dbl> <dbl> <chr>
 1 Rhap-D000…        1 phone     Interval…         0      330.  0     2.23 _    
 2 Rhap-D000…        1 phone     Interval…         0      330.  2.23  2.27 e    
 3 Rhap-D000…        1 phone     Interval…         0      330.  2.27  2.42 s    
 4 Rhap-D000…        1 phone     Interval…         0      330.  2.42  2.45 k    
 5 Rhap-D000…        1 phone     Interval…         0      330.  2.45  2.48 @    
 6 Rhap-D000…        1 phone     Interval…         0      330.  2.48  2.54 v    
 7 Rhap-D000…        1 phone     Interval…         0      330.  2.54  2.65 u    
 8 Rhap-D000…        1 phone     Interval…         0      330.  2.65  2.68 p    
 9 Rhap-D000…        1 phone     Interval…         0      330.  2.68  2.74 u    
10 Rhap-D000…        1 phone     Interval…         0      330.  2.74  2.82 R    
# ℹ 10,536 more rows
# ℹ 1 more variable: annotation_num <int>

Créez un object locuteur qui ne contient que le «tier» “locuteur”.

locuteur <- tg |>
  filter(tier_name == "locuteur") 
locuteur
# A tibble: 91 × 10
   file       tier_num tier_name tier_type tier_xmin tier_xmax  xmin  xmax text 
   <chr>         <int> <chr>     <chr>         <dbl>     <dbl> <dbl> <dbl> <chr>
 1 Rhap-D000…        9 locuteur  Interval…         0      330.  0     9.92 $L1  
 2 Rhap-D000…        9 locuteur  Interval…         0      330.  9.92 11.0  $L2  
 3 Rhap-D000…        9 locuteur  Interval…         0      330. 11.0  11.6  $L2-…
 4 Rhap-D000…        9 locuteur  Interval…         0      330. 11.6  16.9  $L2  
 5 Rhap-D000…        9 locuteur  Interval…         0      330. 16.9  18.2  $L2-…
 6 Rhap-D000…        9 locuteur  Interval…         0      330. 18.2  21.1  $L2  
 7 Rhap-D000…        9 locuteur  Interval…         0      330. 21.1  21.9  $L2-…
 8 Rhap-D000…        9 locuteur  Interval…         0      330. 21.9  36.9  $L2  
 9 Rhap-D000…        9 locuteur  Interval…         0      330. 36.9  37.5  $L3-…
10 Rhap-D000…        9 locuteur  Interval…         0      330. 37.5  38.3  $L3  
# ℹ 81 more rows
# ℹ 1 more variable: annotation_num <int>

Faites un sommaire qui compter les nombre de lignes pour chaque valeur de locuteur.

locuteur |>
  group_by(text) |>
  summarise(n = n())
# A tibble: 9 × 2
  text        n
  <chr>   <int>
1 $L1        12
2 $L1-$L2    10
3 $L1-$L3     1
4 $L2        28
5 $L2-$L1    11
6 $L2-$L3     6
7 $L3        10
8 $L3-$L1     4
9 $L3-$L2     9

En utilisant la fonction stri_count, créez une nouvelle colonne locuteur_nombre dans locuteur qui compte le nombre des signes “$”. Enregistrez les résultats à l’aide du symbole fléché

locuteur <- locuteur |>
  mutate(locuteur_nombre = stri_count(text, fixed = "$"))
locuteur
# A tibble: 91 × 11
   file       tier_num tier_name tier_type tier_xmin tier_xmax  xmin  xmax text 
   <chr>         <int> <chr>     <chr>         <dbl>     <dbl> <dbl> <dbl> <chr>
 1 Rhap-D000…        9 locuteur  Interval…         0      330.  0     9.92 $L1  
 2 Rhap-D000…        9 locuteur  Interval…         0      330.  9.92 11.0  $L2  
 3 Rhap-D000…        9 locuteur  Interval…         0      330. 11.0  11.6  $L2-…
 4 Rhap-D000…        9 locuteur  Interval…         0      330. 11.6  16.9  $L2  
 5 Rhap-D000…        9 locuteur  Interval…         0      330. 16.9  18.2  $L2-…
 6 Rhap-D000…        9 locuteur  Interval…         0      330. 18.2  21.1  $L2  
 7 Rhap-D000…        9 locuteur  Interval…         0      330. 21.1  21.9  $L2-…
 8 Rhap-D000…        9 locuteur  Interval…         0      330. 21.9  36.9  $L2  
 9 Rhap-D000…        9 locuteur  Interval…         0      330. 36.9  37.5  $L3-…
10 Rhap-D000…        9 locuteur  Interval…         0      330. 37.5  38.3  $L3  
# ℹ 81 more rows
# ℹ 2 more variables: annotation_num <int>, locuteur_nombre <int>

Calculez la durée totale selon chaque nombre de locuteur.

locuteur |>
  group_by(locuteur_nombre) |>
  summarise(
    duree = sum(xmax - xmin)
  )
# A tibble: 2 × 2
  locuteur_nombre duree
            <int> <dbl>
1               1 274. 
2               2  55.7

11. Programmer

Le code ci-dessous crée un objet répertoriant tous les fichiers contenant des dialogues tirés de Rhapsodie.

# keep
dir_nom <- "donnees/rhapsodie/tg/"
d <- dir(dir_nom, pattern="TextGrid$")
d <- d[stri_sub(d, 1, 6) == "Rhap-D"]

En modifiant le code ci-dessous, enregistrez les informations relatives aux locuteurs de chaque fichier, y compris le nombre de locuteurs dans chaque point de données.

df <- list("vector", length(d))
for (j in seq_along(d)) {
  tg <- read_textgrid(file.path(dir_nom, d[j]), encoding="UTF-8")
  tg$id <- j

  # ↓↓↓ cette partie ci-dessous fonctionne de traiter une seule
  # ↓↓↓ fiche ; vous pouvez la changer 
  locuteur <- tg |>
    filter(tier_name == "locuteur") |>
    mutate(locuteur_nombre = stri_count(text, fixed = "$"))

  res <- locuteur
  # ↑↑↑

  df[[j]] <- res
}

df <- bind_rows(df)
df
# A tibble: 1,260 × 12
   file       tier_num tier_name tier_type tier_xmin tier_xmax  xmin  xmax text 
   <chr>         <int> <chr>     <chr>         <dbl>     <dbl> <dbl> <dbl> <chr>
 1 Rhap-D000…        9 locuteur  Interval…         0      330.  0     9.92 $L1  
 2 Rhap-D000…        9 locuteur  Interval…         0      330.  9.92 11.0  $L2  
 3 Rhap-D000…        9 locuteur  Interval…         0      330. 11.0  11.6  $L2-…
 4 Rhap-D000…        9 locuteur  Interval…         0      330. 11.6  16.9  $L2  
 5 Rhap-D000…        9 locuteur  Interval…         0      330. 16.9  18.2  $L2-…
 6 Rhap-D000…        9 locuteur  Interval…         0      330. 18.2  21.1  $L2  
 7 Rhap-D000…        9 locuteur  Interval…         0      330. 21.1  21.9  $L2-…
 8 Rhap-D000…        9 locuteur  Interval…         0      330. 21.9  36.9  $L2  
 9 Rhap-D000…        9 locuteur  Interval…         0      330. 36.9  37.5  $L3-…
10 Rhap-D000…        9 locuteur  Interval…         0      330. 37.5  38.3  $L3  
# ℹ 1,250 more rows
# ℹ 3 more variables: annotation_num <int>, id <int>, locuteur_nombre <int>

Calculez la durée totale pour chaque nombre d’intervenants dans l’ensemble de la collection.

df |>
  group_by(locuteur_nombre) |>
  summarise(
    duree = sum(xmax - xmin)
  )
# A tibble: 3 × 2
  locuteur_nombre   duree
            <int>   <dbl>
1               0    3.96
2               1 6848.  
3               2  422.  

12. Données audio

Télécharger un fichier de format «wav» avec le code ci-dessous.

# keep
w <- readWave("donnees/phrase.wav")
snd <- makesound(w@left, fs = w@samp.rate)
snd

      Sound Object

   Read from file:         w@left.wav
   Sampling frequency:     44100  Hz
   Duration:               2844.966  ms
   Number of Samples:      125463 

Créez un ensemble de données formants à l’aide de la fonction formanttrack.

formants <- formanttrack(snd, fs = fs)

Convertissez les données formants en tibble.

formants <- as_tibble(formants)
formants
# A tibble: 383 × 6
    time    f1    f2    f3    f4    f5
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1  30.1  565. 1454. 2295. 3097. 3870.
 2  40.1 2092. 3018. 3823. 4607.    0 
 3 100.     0     0     0     0     0 
 4 200.  1891. 2600. 3983. 4507.    0 
 5 205.   219. 1892. 2606. 3988. 4473.
 6 210.   266. 1961. 2540. 3998. 4429.
 7 215.   306. 1944. 2512. 3980. 4462.
 8 220.   311. 1881. 2504. 3983. 4486.
 9 225.   299. 1870. 2521. 3976. 4508.
10 230.   292. 1878. 2539. 3966. 4515.
# ℹ 373 more rows

J’ai utilisé Praat pour localiser la première voyelle. Elle apparaît entre 300 et 350 millisecondes. Visualisez toutes les valeurs de formants de cette voyelle (un /a/) en utilisant le code similaire des premières sections.

formants |>
  filter(time > 300 & time < 350) |>
  ggplot() +
  geom_point(aes(f2, f1)) +
  scale_x_reverse(limits = c(500, 3500)) +
  scale_y_reverse(limits = c(250, 1400))

13. Analyse grammaticale

Télécharger les données avec le code ci-dessous.

# keep
wikifr <- read_csv2("donnees/wiki_parsed.csv.bz2")
wikifr
# A tibble: 2,431,081 × 11
   doc_id           sid   tid token lemma pos   xpos  dep   dep_head head  morph
   <chr>          <dbl> <dbl> <chr> <chr> <chr> <chr> <chr>    <dbl> <chr> <chr>
 1 Arabie saoudi…     1     1 L     l     NOUN  NOUN  nsubj       10 mona… <NA> 
 2 Arabie saoudi…     1     2 ,     ,     PUNCT PUNCT punct        1 L     <NA> 
 3 Arabie saoudi…     1     3 en    en    ADP   ADP   case         4 forme <NA> 
 4 Arabie saoudi…     1     4 forme forme NOUN  NOUN  nmod         1 L     Gend…
 5 Arabie saoudi…     1     5 long… long  ADJ   ADJ   amod         4 forme Gend…
 6 Arabie saoudi…     1     6 le    le    DET   DET   det         10 mona… Defi…
 7 Arabie saoudi…     1     7 ,     ,     PUNCT PUNCT punct       10 mona… <NA> 
 8 Arabie saoudi…     1     8 est   être  AUX   AUX   cop         10 mona… Mood…
 9 Arabie saoudi…     1     9 une   un    DET   DET   det         10 mona… Defi…
10 Arabie saoudi…     1    10 mona… mona… NOUN  NOUN  ROOT         0 ROOT  Gend…
# ℹ 2,431,071 more rows

Sélectionnez uniquement les lignes dont la relation de dépendance (dep) est égale à “det” (déterminant). Sélectionnez uniquement celles dont les lemmes commencent par “le” et “un”. Ensuite, en fonction du mot principal (head), résumez la proportion moyenne de lemmes égale à “le”. Filtrez uniquement les mots principaux qui apparaissent au moins 600 fois et triez les résultats par ordre croissant.

wikifr |>
  filter(dep == "det") |>
  filter(lemma %in% c("le", "un")) |>
  group_by(head) |>
  summarise(
    prop_le = mean(lemma == "le"),
    n = n()
  ) |>
  filter(n > 600) |>
  arrange(desc(prop_le))
# A tibble: 15 × 3
   head    prop_le     n
   <chr>     <dbl> <int>
 1 fin       0.981  1079
 2 suite     0.968   968
 3 année     0.957   789
 4 ville     0.945  1289
 5 commune   0.939   979
 6 époque    0.919   607
 7 guerre    0.918   661
 8 nom       0.918   645
 9 région    0.914   709
10 années    0.871   821
11 place     0.778   720
12 période   0.756   620
13 fois      0.701   916
14 «         0.654  1359
15 partie    0.502   819

14. ACP + UMAP

Télécharger les données avec le code ci-dessous.

# keep
pays <- read_csv2("donnees/pays.csv")
pays
# A tibble: 187 × 5
   pays        code2 code3 region    sous_region       
   <chr>       <chr> <chr> <chr>     <chr>             
 1 Afghanistan AF    AFG   Asie      Asie du Sud       
 2 Albanie     AL    ALB   Europe    Europe du Sud     
 3 Algérie     DZ    DZA   Afrique   Afrique du Nord   
 4 Allemagne   DE    DEU   Europe    Europe occidentale
 5 Andorre     AD    AND   Europe    Europe du Sud     
 6 Angola      AO    AGO   Afrique   Afrique centrale  
 7 Anguilla    AI    AIA   Amériques Caraïbes          
 8 Argentine   AR    ARG   Amériques Amérique du Sud   
 9 Arménie     AM    ARM   Asie      Asie occidentale  
10 Aruba       AW    ABW   Amériques Caraïbes          
# ℹ 177 more rows

Télécharger les plongements avec :

# keep
embed <- read_rds("donnees/fasttext_embed.rds")
idx <- match(pays$pays, rownames(embed))
X <- embed[idx, ]
dim(X)
[1] 187 300

Creéz les ACP et les ajoutez à l’objet pays

apc <- prcomp(X, center = TRUE, scale. = TRUE)
pays$apc1 <- apc$x[, 1]
pays$apc2 <- apc$x[, 2]
pays
# A tibble: 187 × 7
   pays        code2 code3 region    sous_region           apc1   apc2
   <chr>       <chr> <chr> <chr>     <chr>                <dbl>  <dbl>
 1 Afghanistan AF    AFG   Asie      Asie du Sud          2.45   1.43 
 2 Albanie     AL    ALB   Europe    Europe du Sud        4.13  -4.87 
 3 Algérie     DZ    DZA   Afrique   Afrique du Nord      4.80   0.313
 4 Allemagne   DE    DEU   Europe    Europe occidentale   8.19  -6.66 
 5 Andorre     AD    AND   Europe    Europe du Sud        0.381 -6.73 
 6 Angola      AO    AGO   Afrique   Afrique centrale     2.11   3.25 
 7 Anguilla    AI    AIA   Amériques Caraïbes           -10.6   -4.88 
 8 Argentine   AR    ARG   Amériques Amérique du Sud      4.29  -2.75 
 9 Arménie     AM    ARM   Asie      Asie occidentale     2.71  -3.92 
10 Aruba       AW    ABW   Amériques Caraïbes            -7.15  -4.14 
# ℹ 177 more rows

Visualizez les ACP comme dans les notes avec couleur associé avec region.

pays |>
  ggplot(aes(apc1, apc2)) +
    geom_point(aes(colour = region)) +
    geom_text(
      aes(label = pays, colour = region),
      size = 2,
      nudge_y = -0.25
    )

Créez un nouveau tableau europe qui contient les pays en Europe. Puis, créez les APC, ajoutez au tableau et visualisez avec la colonne sous_region.

europe <- pays |>
  filter(region == "Europe")

embed <- read_rds("donnees/fasttext_embed.rds")
idx <- match(europe$pays, rownames(embed))
X <- embed[idx, ]
dim(X)
[1]  47 300
apc <- prcomp(X, center = TRUE, scale. = TRUE)
europe$apc1 <- apc$x[, 1]
europe$apc2 <- apc$x[, 2]

europe |>
  ggplot(aes(apc1, apc2)) +
    geom_point(aes(colour = sous_region)) +
    geom_text(
      aes(label = pays, colour = sous_region),
      size = 2,
      nudge_y = -0.25
    )

Créez les plongement UMAP et les ajoutez au tableau europe. Visulisez encore dans la même façon.

obj <- umap(X)
europe$umap1 <- obj$layout[, 1]
europe$umap2 <- obj$layout[, 2]

europe |>
  ggplot(aes(umap1, umap2)) +
    geom_point(aes(colour = sous_region)) +
    geom_text(
      aes(label = pays, colour = sous_region),
      size = 2,
      nudge_y = -0.1
    )

15. Modèles prédictifs

Télécharger les données avec le code ci-dessous.

# keep
parse <- read_csv2("donnees/polarity_parse.csv.bz2")
meta <- read_csv2("donnees/polarity_meta.csv.bz2")

Créez un object X comme dans les notes mais utilisez la colonne pos au lieu de lemma.

X <- parse |>
  to_sparse(doc_id, pos)
dim(X)
[1] 160000     16

Créez des objets df_meta, ind_train et ind_valid en utilisant le code ci-dessous.

# keep
df_meta <- tibble(doc_id = as.numeric(rownames(X))) |>
  left_join(meta, by = "doc_id")
df_meta
# A tibble: 160,000 × 2
   doc_id polarity
    <dbl>    <dbl>
 1      0        0
 2      1        0
 3      2        0
 4      3        0
 5      4        1
 6      5        0
 7      6        1
 8      7        1
 9      8        1
10      9        0
# ℹ 159,990 more rows
ind_train <- sort(sample(seq_len(nrow(X)), 10000))
ind_valid <- setdiff(seq_len(nrow(X)), ind_train)

Créez un modèle prédictif avec les données.

model <- cv.glmnet(
  X[ind_train,], df_meta$polarity[ind_train], family="binomial"
)

Évaluez les taux de classification sur les données validations.

pred <- predict(model, newx=X[ind_valid,], type = "class")
mean(pred == df_meta$polarity[ind_valid])
[1] 0.6159133

Créez une matrice de confusion.

table(pred=pred, y=df_meta$polarity[ind_valid])
    y
pred     0     1
   0 49568 32780
   1 24833 42819

Régardez les coefficients du modèle. Vous pouvez donner l’argument s = model$lambda.1se à la fonction coef.

cf <- coef(model, s = model$lambda.1se)
cf <- cf[cf[,1] != 0,,drop=FALSE][-1,,drop=FALSE]
cf[order(cf[,1]),,drop=FALSE]
14 x 1 sparse Matrix of class "dgCMatrix"
      s=0.002934966
INTJ  -1.1688783939
SYM   -0.5414372573
ADV   -0.0859389617
SCONJ -0.0718171092
NUM   -0.0643632430
VERB  -0.0216020432
X     -0.0180941782
PUNCT -0.0084461177
PRON  -0.0039273688
ADP   -0.0009441389
PROPN  0.0350494345
ADJ    0.0351787932
DET    0.0576269756
CCONJ  0.0740959470

16. LLM

Télécharger les données avec le code ci-dessous et créez les objets X, df_meta, ind_train et ind_valid.

# keep
llm <- read_csv2("donnees/polarity_llm.csv.bz2")
llm
# A tibble: 953 × 7
   doc_id input                        polarity raw   conviction certain positif
    <dbl> <chr>                           <dbl> <chr>      <dbl>   <dbl>   <dbl>
 1     39 "Petit film amateur réalisé…        0 "{\"…          6       7       5
 2    132 "Une comédie romantique bie…        1 "{\"…          7       8       9
 3    618 "Le sujet est d'une sensibi…        1 "{\"…          9       9      10
 4    720 "Structure narrative classi…        0 "{\"…          8       7       2
 5    859 "Un film énigmatique dans s…        1 "{\"…          5       4       7
 6    989 "Fortunata n'a pas la vie f…        0 "{\"…          6       8       2
 7   1781 "j'ai vu les deux films et …        1 "{\"…          8       7       9
 8   1817 "Très déçue. Comme trop sou…        0 "{\"…          8       7       2
 9   1852 "Antonio Banderas s'investi…        0 "{\"…          9       9       1
10   1982 "Mais qu est ce que c est n…        0 "{\"…          6       8       2
# ℹ 943 more rows
X <- parse |>
  mutate(lemma = stri_trans_tolower(lemma)) |>
  group_by(lemma) |>
  filter(n() > 200) |>
  to_sparse(doc_id, lemma)
df_meta <- tibble(doc_id = as.numeric(rownames(X))) |>
  left_join(meta, by = "doc_id")
ind_train <- sort(sample(seq_len(nrow(X)), 10000))
ind_valid <- setdiff(seq_len(nrow(X)), ind_train)

Créez un modèle prédictif pour les valeurs certain.

X_nouv <- X[as.character(llm$doc_id),]
model <- cv.glmnet(X_nouv, llm$certain)

Régardez les coefficients du modèle.

cf <- coef(model, s = model$lambda[12])
cf <- cf[cf[,1] != 0,,drop=FALSE][-1,,drop=FALSE]
cf[order(cf[,1]),,drop=FALSE]
23 x 1 sparse Matrix of class "dgCMatrix"
            s=0.1556286
jet       -1.069501e+00
vieillot  -1.006177e+00
euro      -9.973597e-01
obséder   -8.847892e-01
remarqu   -6.467856e-01
inaperçu  -5.262346e-01
vaste     -4.777187e-01
camion    -4.625296e-01
j.        -1.767389e-01
liam      -1.332088e-01
définitif -1.223710e-01
document  -6.979123e-02
ninja     -6.950074e-02
pauvreté  -6.936393e-02
tete      -6.366322e-02
côtè      -5.873196e-02
?         -3.036391e-02
grain     -1.442076e-04
neeson    -4.364034e-06
aucun      5.749573e-02
film       6.370499e-02
acteur     1.136914e-01
ridicule   2.119578e-01

Vous pouvez répéter avec les autres colonnes de llm.

17. Whisper

Télécharger les données avec le code ci-dessous.

# keep
whisper_mots <- read_csv2("donnees/whisper_mots.csv")
whisper_mots
# A tibble: 905 × 3
   word     start   end
   <chr>    <dbl> <dbl>
 1 C         0     0.32
 2 est       0.32  0.32
 3 pas       0.32  1.52
 4 mal       1.52  1.74
 5 disons    1.74  2.2 
 6 est       2.4   2.46
 7 ce        2.46  2.46
 8 que       2.46  2.48
 9 vous      2.48  2.66
10 pourriez  2.66  3.34
# ℹ 895 more rows

Nous allons étudier ce tableau dans section 19.

18. Plongement de textes

Télécharger les données avec le code ci-dessous et créez les objets ind_train et ind_valid.

# keep
X <- read_rds("donnees/polarity_embed.rds")
ind_train <- sort(sample(seq_len(nrow(X)), 700))
ind_valid <- setdiff(seq_len(nrow(X)), ind_train)

Créez un modèle pour certain comme dans les notes.

model <- cv.glmnet(
  X[ind_train,], llm$certain[ind_train], alpha=0
)

Éxecutez le code ci-dessous de créer des prédictions.

# keep
df <- tibble(
  certain = llm$certain[ind_valid],
  pred = as.numeric(predict(model, newx=X[ind_valid,], type = "class"))
)
df
# A tibble: 253 × 2
   certain  pred
     <dbl> <dbl>
 1       8  6.88
 2       2  6.72
 3       8  6.84
 4       4  6.49
 5       6  6.43
 6       8  6.31
 7       8  6.70
 8       8  7.95
 9       4  6.93
10       8  7.03
# ℹ 243 more rows

Visualiser la relation entre les valeurs certain et les prédictions.

df |>
  ggplot() +
  geom_point(aes(factor(certain), pred))

19. Alignement des textes

Télécharger les données avec le code ci-dessous et créer un tableau tg_mots.

# keep
tg <- read_textgrid(
  "donnees/rhapsodie/tg/Rhap-D0001-Pro.TextGrid",
  encoding="UTF-8"
)
tg_mots <- tg |>
  filter(tier_name == "words") |>
  filter(text != "_") |>
  select(text, xmin, xmax)

Utiliser la fonction alignement_des_textes pour aligner tg_mots et whisper_mots. Sauvgarder les données dans un objet df_comb.

df_comb <- alignement_des_textes(
  tg_mots,
  whisper_mots,
  col1 = text,
  col2 = word,
  suffix_y = "_whisper"
)
df_comb
# A tibble: 597 × 8
      id  id_y text          xmin  xmax word_whisper start_whisper end_whisper
   <dbl> <dbl> <chr>        <dbl> <dbl> <chr>                <dbl>       <dbl>
 1     2     9 vous          2.48  2.65 vous                  2.48        2.66
 2     3    10 pourriez      2.65  3.30 pourriez              2.66        3.34
 3     4    11 décrire       3.48  4.32 décrire               3.34        4.2 
 4     6    12 les           5.73  5.88 les                   4.2         5.98
 5     7    13 déplacements  5.88  6.62 déplacements          5.98        6.52
 6     8    14 avec          6.62  6.88 avec                  6.52        6.88
 7     9    15 précision     6.88  7.48 précision             6.88        8.5 
 8    10    16 une           9.21  9.36 une                   9.14        9.48
 9    11    17 journée       9.36  9.92 journée               9.48        9.78
10    16    22 on           10.8  11.0  on                   10.8        11.0 
# ℹ 587 more rows

Terminez par calculer les differences moyenne de xmin et xmax pour les deux données. Vous pouvez ajouter la fonction abs().

df_comb |>
  mutate(
    diff_min = xmin - start_whisper,
    diff_max = xmax - end_whisper
  ) |>
  summarise(
    diff_min_moyenne = mean(abs(diff_min)),
    diff_max_moyenne = mean(abs(diff_max))   
  )
# A tibble: 1 × 2
  diff_min_moyenne diff_max_moyenne
             <dbl>            <dbl>
1            0.212            0.175