Les salaires dans le secteur privé en 2025 - Le pouvoir d’achat des salaires progresse pour la deuxième année consécutive - IP2132

Données - INSEE

Figure 1 : salaires mensuels moyens et répartition des effectifs en EQTP, en 2025

Salaire mensuel net moyen

Code
fig1 |>
  mutate(categorie = fct_inorder(categorie),
         dimension = fct_inorder(dimension)) |>
  ggplot(aes(x = fct_rev(categorie), y = net_2025, fill = categorie == "Ensemble")) +
  geom_col(width = 0.7) +
  geom_text(aes(label = format(net_2025, big.mark = " ")), hjust = -0.15, size = 3.5) +
  coord_flip(clip = "off") +
  facet_grid(dimension ~ ., scales = "free_y", space = "free_y") +
  scale_fill_manual(values = c("FALSE" = bleu, "TRUE" = rouge), guide = "none") +
  scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = "Euros par mois",
       title = "Salaire mensuel net moyen en EQTP, en 2025") +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major.y = element_blank(),
        strip.text.y = element_text(angle = 0, hjust = 0))

Évolution 2025/2024, en euros constants

Code
fig1 |>
  mutate(categorie = fct_inorder(categorie),
         dimension = fct_inorder(dimension)) |>
  select(dimension, categorie,
         `Salaire net moyen en EQTP` = net_evol_constants,
         `RMPP nette` = rmpp_net_evol_constants) |>
  pivot_longer(-c(dimension, categorie), names_to = "indicateur", values_to = "evolution") |>
  mutate(indicateur = factor(indicateur, levels = c("RMPP nette", "Salaire net moyen en EQTP"))) |>
  ggplot(aes(x = fct_rev(categorie), y = evolution, fill = indicateur)) +
  geom_col(position = position_dodge(width = 0.75), width = 0.7) +
  coord_flip() +
  facet_grid(dimension ~ ., scales = "free_y", space = "free_y") +
  scale_fill_manual(values = c("Salaire net moyen en EQTP" = bleu, "RMPP nette" = bleu_clair), name = NULL,
                    breaks = c("Salaire net moyen en EQTP", "RMPP nette")) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Évolution 2025/2024 des salaires nets, en euros constants",
       subtitle = "en % ; RMPP = rémunération moyenne des personnes en place") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank(),
        panel.grid.major.y = element_blank(),
        strip.text.y = element_text(angle = 0, hjust = 0))

Table

Code
fig1 |>
  select(-dimension) |>
  print_table_conditional()
categorie brut_2025 brut_evol_courants brut_evol_constants net_2025 net_evol_courants net_evol_constants part_effectifs rmpp_net_evol_constants
Cadres 6342 2.2 1.2 4711 2.1 1.1 23.5 3.3
Professions intermédiaires 3545 2.1 1.1 2681 1.9 1.0 20.5 2.7
Employés 2553 2.2 1.2 1979 2.1 1.1 27.8 2.6
Ouvriers 2717 2.3 1.3 2092 2.0 1.1 28.2 2.2
Femmes 3410 2.8 1.9 2578 2.7 1.7 42.0 2.9
Hommes 3898 2.5 1.6 2956 2.4 1.4 58.0 2.8
Industrie 4173 2.7 1.8 3094 2.4 1.5 16.3 2.6
Construction 3245 3.4 2.4 2487 3.2 2.3 8.0 3.0
Tertiaire 3637 2.6 1.6 2766 2.4 1.5 75.7 2.9
Ensemble 3693 2.6 1.7 2797 2.5 1.5 100.0 2.8

Figure 2 : évolution du salaire net moyen en EQTP depuis 1996, en euros constants

Linéaire

Code
plot_fig2 <- fig2 |>
  ggplot(aes(x = annee, y = moyenne)) +
  geom_line(color = bleu, linewidth = 1.2) +
  geom_point(color = bleu, size = 1.5) +
  geom_hline(yintercept = 100, color = "grey60", linetype = "dashed") +
  scale_x_continuous(breaks = seq(1996, 2026, 2)) +
  scale_y_continuous(breaks = seq(90, 130, 2)) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Salaire net moyen en EQTP, en euros constants",
       subtitle = "Indice base 100 en 1996 - Échelle linéaire") +
  theme(panel.grid.minor = element_blank())

plot_fig2

Log

Code
plot_fig2 +
  scale_y_log10(breaks = seq(90, 130, 2)) +
  labs(subtitle = "Indice base 100 en 1996 - Échelle logarithmique")

Évolution annuelle (tableau complémentaire 4)

Code
tc4 |>
  ggplot(aes(x = annee, y = evol_salaire_net, fill = evol_salaire_net > 0)) +
  geom_col() +
  geom_hline(yintercept = 0, color = "grey40", linewidth = 0.4) +
  geom_text(aes(label = evol_salaire_net,
                vjust = if_else(evol_salaire_net >= 0, -0.4, 1.3)), size = 3) +
  scale_fill_manual(values = c("TRUE" = bleu, "FALSE" = rouge), guide = "none") +
  scale_x_continuous(breaks = seq(1997, 2025, 2)) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Évolution annuelle du salaire net moyen en EQTP, en euros constants",
       subtitle = "en %") +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank())

Figure 3 : distribution des salaires mensuels nets en EQTP, en 2025

Code
fig3 |>
  mutate(centre = coalesce((borne_inf + borne_sup) / 2, borne_inf + 50, borne_sup - 50)) |>
  ggplot(aes(x = centre, y = effectifs_milliers)) +
  geom_col(fill = bleu, width = 90) +
  scale_x_continuous(breaks = seq(1000, 9000, 1000),
                     labels = scales::label_number(big.mark = " ")) +
  theme_minimal(base_size = 12) +
  labs(x = "Salaire mensuel net (en euros)", y = "Milliers d'EQTP",
       title = "Distribution des salaires mensuels nets en EQTP, en 2025",
       subtitle = "Tranches de 100 euros\npremière barre : moins de 1 300 euros ; dernière : 9 000 euros ou plus") +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank())

Centiles (tableau complémentaire 3)

Code
tc3 |>
  ggplot(aes(x = centile, y = salaire_net)) +
  geom_line(color = bleu, linewidth = 1.2) +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_log10(breaks = c(1500, 2000, 3000, 4000, 5000, 7500, 10000),
                labels = scales::label_number(big.mark = " ")) +
  theme_minimal(base_size = 12) +
  labs(x = "Centile", y = "Euros par mois",
       title = "Salaire mensuel net par centile, en 2025",
       subtitle = "Échelle logarithmique") +
  theme(panel.grid.minor = element_blank())

Log-log (Pareto)

Part des salariés dont le salaire mensuel net est supérieur ou égal à \(s\), en fonction de \(s\), les deux axes en échelle logarithmique. Si la queue de distribution est de Pareto, \(P(S \geq s) \propto s^{-\alpha}\) et les points sont alignés sur une droite de pente \(-\alpha\). Points : bornes inférieures des tranches de la figure 3 ; droite : régression sur les tranches à partir de 3 000 euros.

Code
N_total <- sum(fig3$effectifs_milliers)
pareto <- fig3 |>
  filter(!is.na(borne_inf)) |>
  arrange(borne_inf) |>
  mutate(part_au_dessus = rev(cumsum(rev(effectifs_milliers))) / N_total)

seuil_pareto <- 3000
fit_pareto <- lm(log(part_au_dessus) ~ log(borne_inf), data = filter(pareto, borne_inf >= seuil_pareto))
alpha <- -coef(fit_pareto)[2]

pareto |>
  mutate(ajuste = exp(predict(fit_pareto, newdata = pick(everything()))),
         ajuste = if_else(ajuste <= 1, ajuste, NA_real_)) |>
  ggplot(aes(x = borne_inf, y = part_au_dessus)) +
  geom_point(color = bleu, size = 1.5) +
  geom_line(aes(y = ajuste), color = rouge, linewidth = 0.8, linetype = "dashed", na.rm = TRUE) +
  annotate("text", x = 5000, y = 0.3, color = rouge, hjust = 0, size = 4,
           label = paste0("Pente : -", format(round(alpha, 2), decimal.mark = ","),
                          "\nR² = ", format(sprintf("%.4f", summary(fit_pareto)$r.squared)) |> str_replace("\\.", ","))) +
  scale_x_log10(breaks = c(1300, 1500, 2000, 3000, 4000, 5000, 7000, 9000),
                labels = scales::label_number(big.mark = " ")) +
  scale_y_log10(breaks = c(0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1),
                labels = scales::label_percent(accuracy = 1)) +
  theme_minimal(base_size = 12) +
  labs(x = "Salaire mensuel net s (en euros)", y = "Part des EQTP avec un salaire ≥ s",
       title = "Distribution des salaires mensuels nets en EQTP, en 2025",
       subtitle = "Échelle log-log (Pareto) ; droite ajustée à partir de 3 000 euros") +
  theme(panel.grid.minor = element_blank())

Log-log (Pareto) : comparaison 2024 - 2025

On ne compare la distribution de 2025 qu’à celle de 2024 (IP2079), qui a le même champ : privé, y compris apprentis et stagiaires. Les histogrammes publiés jusqu’en 2023 excluent les apprentis et stagiaires. Ils ne sont donc pas comparables à ceux de 2025 (voir plus bas, et la page sur toutes les éditions).

Code
part_au_dessus <- function(f) {
  N <- sum(f$effectifs_milliers)
  f |>
    filter(!is.na(borne_inf)) |>
    arrange(borne_inf) |>
    mutate(part_au_dessus = rev(cumsum(rev(effectifs_milliers))) / N)
}

medianes <- c("2024" = champ_2024 |> filter(quantile == "Médiane") |> pull(avec_niveau),
              "2025" = fig4 |> filter(sexe == "Ensemble", quantile == "Médiane") |> pull(niveau))

pareto_2024_2025 <- bind_rows(
  part_au_dessus(histogrammes |> filter(annee == 2024)) |> mutate(annee = "2024"),
  part_au_dessus(fig3) |> mutate(annee = "2025")
) |>
  mutate(relatif = borne_inf / medianes[annee])

En euros courants

Code
pareto_2024_2025 |>
  ggplot(aes(x = borne_inf, y = part_au_dessus, color = annee)) +
  geom_point(size = 1.3) +
  scale_color_manual(values = c("2024" = bleu_clair, "2025" = bleu), name = NULL) +
  scale_x_log10(breaks = c(1300, 1500, 2000, 3000, 4000, 5000, 7000, 9000),
                labels = scales::label_number(big.mark = " ")) +
  scale_y_log10(breaks = c(0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1),
                labels = scales::label_percent(accuracy = 1)) +
  theme_minimal(base_size = 12) +
  labs(x = "Salaire mensuel net s (en euros courants)", y = "Part des EQTP avec un salaire ≥ s",
       title = "Distribution des salaires mensuels nets en EQTP, 2024 et 2025",
       subtitle = "Échelle log-log (Pareto), euros courants, même champ") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank())

En multiples du salaire médian

Diviser par la médiane de chaque année (2 190 euros en 2024, 2 238 euros en 2025) neutralise la hausse générale des salaires. Si les courbes se superposent, la dispersion relative des salaires n’a pas changé.

Code
pareto_2024_2025 |>
  ggplot(aes(x = relatif, y = part_au_dessus, color = annee)) +
  geom_point(size = 1.3) +
  scale_color_manual(values = c("2024" = bleu_clair, "2025" = bleu), name = NULL) +
  scale_x_log10(breaks = c(0.6, 0.8, 1, 1.5, 2, 3, 4)) +
  scale_y_log10(breaks = c(0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1),
                labels = scales::label_percent(accuracy = 1)) +
  theme_minimal(base_size = 12) +
  labs(x = "Salaire mensuel net s / salaire médian", y = "Part des EQTP avec un salaire ≥ s",
       title = "Distribution des salaires mensuels nets en EQTP, 2024 et 2025",
       subtitle = "Échelle log-log (Pareto), en multiples du salaire médian de l'année") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank())

Coefficients de Pareto, 2017-2025

La pente α de la queue peut, elle, être comparée malgré les changements de champ. Ajouter des salariés payés sous le seuil (apprentis, stagiaires) multiplie toutes les parts au-dessus du seuil par un même facteur : la droite log-log se déplace, mais sa pente ne change pas.

Code
alpha_pareto <- function(d, seuil) {
  m <- lm(log(part_au_dessus) ~ log(borne_inf), data = filter(d, borne_inf >= seuil))
  tibble(alpha = -unname(coef(m)[2]))
}

histogrammes |>
  group_by(annee, champ) |>
  group_modify(~ part_au_dessus(.x)) |>
  ungroup() |>
  nest(data = -c(annee, champ)) |>
  crossing(seuil = c(3000, 4000, 5000)) |>
  mutate(res = map2(data, seuil, alpha_pareto)) |>
  unnest(res) |>
  select(-data) |>
  mutate(alpha = round(alpha, 3), seuil = paste0("α (à partir de ", seuil, " euros)")) |>
  pivot_wider(names_from = seuil, values_from = alpha) |>
  print_table_conditional()
annee champ α (à partir de 3000 euros) α (à partir de 4000 euros) α (à partir de 5000 euros)
2017 2011-2020 : privé et entreprises publiques, hors apprentis et stagiaires 2.671 2.671 2.652
2018 2011-2020 : privé et entreprises publiques, hors apprentis et stagiaires 2.601 2.615 2.607
2019 2011-2020 : privé et entreprises publiques, hors apprentis et stagiaires 2.672 2.685 2.680
2020 2011-2020 : privé et entreprises publiques, hors apprentis et stagiaires 2.635 2.659 2.659
2021 2021-2023 : privé, hors apprentis et stagiaires 2.635 2.652 2.644
2022 2021-2023 : privé, hors apprentis et stagiaires 2.641 2.664 2.659
2023 2021-2023 : privé, hors apprentis et stagiaires 2.662 2.684 2.673
2024 2024-2025 : privé, y compris apprentis et stagiaires 2.668 2.694 2.684
2025 2024-2025 : privé, y compris apprentis et stagiaires 2.657 2.683 2.667

Comparaison 2017 - 2025 à champ constant, en euros constants

Les centiles publiés en 2017 (IP1798) et en 2025 ne sont pas comparables. En 2017, le champ exclut les apprentis et stagiaires, mais inclut les entreprises publiques. En 2025, il inclut les apprentis et stagiaires, mais pas les entreprises publiques. L’IP2079 mesure l’effet de l’entrée des apprentis et stagiaires (et de Mayotte) en 2024. Peu payés, ils se concentrent sous le D1 et font baisser tous les quantiles, car chaque rang se décale :

Code
champ_2024 |>
  filter(type == "Quantile") |>
  transmute(Quantile = quantile,
            `2024, sans apprentis ni stagiaires` = sans_niveau,
            `2024, avec apprentis et stagiaires` = avec_niveau,
            `Effet du champ (%)` = round(100 * (avec_niveau / sans_niveau - 1), 1)) |>
  print_table_conditional()
Quantile 2024, sans apprentis ni stagiaires 2024, avec apprentis et stagiaires Effet du champ (%)
1er décile (D1) 1556 1492 -4.1
2e décile 1721 1669 -3.0
3e décile 1875 1823 -2.8
4e décile 2045 1992 -2.6
Médiane 2247 2190 -2.5
6e décile 2503 2442 -2.4
7e décile 2850 2785 -2.3
8e décile 3378 3305 -2.2
9e décile (D9) 4422 4334 -2.0
95e centile 5704 5593 -1.9
99e centile 10478 10261 -2.1
Moyenne 2814 2733 -2.9

On utilise donc les séries longues de l’Insee. Elles portent sur un champ homogène, y compris apprentis et stagiaires, rétropolé depuis 1996, et sont prolongées en 2024 et 2025 par les évolutions à champ constant de l’IP2079 et de l’IP2132. Les prix ont augmenté de 22,6 % entre 2017 et 2025 selon l’IPCH, et de 19,5 % selon l’IPC (ensemble des ménages), le déflateur des « euros constants » de l’Insee.

Code
evol_quantiles_2017 <- series |>
  filter(annee %in% c(2017, 2025), indicateur %in% c("D1", "D5", "D9", "C95", "C99", "MOYENNE")) |>
  select(annee, indicateur, nominal) |>
  pivot_wider(names_from = annee, values_from = nominal, names_prefix = "salaire_net_") |>
  crossing(prix |> select(indice, deflateur)) |>
  mutate(evolution = 100 * (salaire_net_2025 / (salaire_net_2017 * deflateur) - 1),
         quantile = factor(recode(indicateur, D5 = "Médiane", C95 = "P95", C99 = "P99", MOYENNE = "Moyenne"),
                           levels = c("D1", "Médiane", "D9", "P95", "P99", "Moyenne")),
         indice = paste0("Déflateur : ", indice))

evol_quantiles_2017 |>
  filter(quantile != "Moyenne") |>
  ggplot(aes(x = quantile, y = evolution, color = indice, group = indice)) +
  geom_hline(yintercept = 0, color = "grey40", linewidth = 0.4) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2) +
  geom_text(aes(label = format(round(evolution, 1), decimal.mark = ",")), vjust = -0.9, size = 3, show.legend = FALSE) +
  scale_color_manual(values = c("Déflateur : IPCH" = bleu, "Déflateur : IPC" = bleu_clair), name = NULL) +
  scale_y_continuous(breaks = seq(-10, 10, 1), expand = expansion(mult = c(0.1, 0.2))) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Évolution du salaire mensuel net en EQTP entre 2017 et 2025",
       subtitle = "en %, en euros constants, à champ constant (séries longues de l'Insee)") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank())

Code
evol_quantiles_2017 |>
  select(quantile, salaire_net_2017, salaire_net_2025, indice, evolution) |>
  mutate(across(starts_with("salaire"), round), evolution = round(evolution, 1)) |>
  pivot_wider(names_from = indice, values_from = evolution) |>
  arrange(quantile) |>
  print_table_conditional()
quantile salaire_net_2017 salaire_net_2025 Déflateur : IPC Déflateur : IPCH
D1 1232 1521 3.3 0.7
Médiane 1814 2240 3.3 0.7
D9 3655 4445 1.8 -0.8
P95 4774 5744 0.7 -1.9
P99 8816 10590 0.5 -2.0
Moyenne 2286 2801 2.5 -0.1

Par centile : centiles publiés et séries longues

La figure reprend la présentation centile par centile (en abscisse, le centile ; en ordonnée, l’évolution réelle entre 2017 et 2025) et superpose trois mesures :

  • Centiles publiés, sans correction (gris) : centiles 2025 de l’IP2132 rapportés aux centiles 2017 de l’IP1798. Les champs diffèrent (apprentis et stagiaires inclus en 2025 seulement) et les sources aussi (DADS en 2017, DSN ensuite).
  • Centiles publiés, corrigés des apprentis (pointillés) : la courbe grise corrigée de l’effet de l’entrée des apprentis, des stagiaires et de Mayotte, mesuré par l’Insee en 2024 (IP2079, encadré), interpolé entre les déciles. Cette correction ne traite pas le changement de source entre 2017 et 2025.
  • Séries longues de l’Insee (points) : champ homogène et rétropolé. Elles ne donnent que quelques quantiles (D1, médiane, D9, P95, P99), mais elles reproduisent l’indice officiel de la figure 5.
Code
correction_champ <- champ_2024 |>
  filter(type == "Quantile", quantile != "Moyenne") |>
  mutate(centile = c(10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99),
         effet = avec_niveau / sans_niveau)

prix_2017_2025 <- prix |> select(indice, deflateur)

publies <- centiles_editions |>
  filter(annee %in% c(2017, 2025)) |>
  select(annee, centile, salaire_net) |>
  pivot_wider(names_from = annee, values_from = salaire_net, names_prefix = "s") |>
  mutate(effet = approx(correction_champ$centile, correction_champ$effet, xout = centile)$y) |>
  crossing(prix_2017_2025) |>
  transmute(centile, indice,
            `Centiles publiés, sans correction` = 100 * (s2025 / (s2017 * deflateur) - 1),
            `Centiles publiés, corrigés des apprentis` = 100 * (s2025 / effet / (s2017 * deflateur) - 1)) |>
  pivot_longer(starts_with("Centiles"), names_to = "mesure", values_to = "evolution") |>
  filter(!is.na(evolution))

longues <- evol_quantiles_2017 |>
  filter(quantile != "Moyenne") |>
  mutate(centile = c(D1 = 10, Médiane = 50, D9 = 90, P95 = 95, P99 = 99)[as.character(quantile)],
         indice = str_remove(indice, "Déflateur : "),
         mesure = "Séries longues de l'Insee (champ constant)")

bind_rows(publies, longues |> select(centile, indice, mesure, evolution)) |>
  mutate(indice = paste0("Déflateur : ", indice),
         mesure = factor(mesure, levels = c("Centiles publiés, sans correction",
                                            "Centiles publiés, corrigés des apprentis",
                                            "Séries longues de l'Insee (champ constant)"))) |>
  ggplot(aes(x = centile, y = evolution, color = mesure, linetype = mesure)) +
  geom_hline(yintercept = 0, color = "grey40", linewidth = 0.4) +
  geom_line(linewidth = 1) +
  geom_point(data = ~ filter(.x, mesure == "Séries longues de l'Insee (champ constant)"), size = 2.5) +
  facet_wrap(~ indice) +
  scale_color_manual(values = c("Centiles publiés, sans correction" = "grey65",
                                "Centiles publiés, corrigés des apprentis" = bleu_clair,
                                "Séries longues de l'Insee (champ constant)" = rouge), name = NULL) +
  scale_linetype_manual(values = c("Centiles publiés, sans correction" = "solid",
                                   "Centiles publiés, corrigés des apprentis" = "dashed",
                                   "Séries longues de l'Insee (champ constant)" = "solid"), name = NULL) +
  scale_x_continuous(breaks = c(5, seq(10, 90, 10), 99)) +
  scale_y_continuous(breaks = seq(-6, 6, 1)) +
  guides(color = guide_legend(ncol = 1), linetype = guide_legend(ncol = 1)) +
  theme_minimal(base_size = 12) +
  labs(x = "Centile de salaire net en EQTP", y = NULL,
       title = "Salaire net en EQTP par centile, évolution entre 2017 et 2025",
       subtitle = "en %, en euros constants") +
  theme(legend.position = "bottom", panel.grid.minor = element_blank())

Par centile, 2017-2023 : séries longues seules, sans prolongement

Jusqu’en 2023, les séries longues donnent aussi P25 et P75 et ne demandent aucun prolongement.

Code
SLS2025_EQTP08 |>
  filter(sexe == "Ensemble", annee %in% c(2017, 2023),
         indicateur %in% c("D1", "Q1", "D5", "Q3", "D9", "C95", "C99")) |>
  pivot_wider(names_from = annee, values_from = value, names_prefix = "s") |>
  crossing(prix_2017_2023 = c("IPC", "IPCH")) |>
  left_join(
    bind_rows(
      read_parquet("IPCH-2025.parquet") |>
        filter(COICOP2018 == "00", NATURE == "INDICE", REF_AREA == "FE", FREQ == "A") |> mutate(indice = "IPCH"),
      read_parquet("IPC-2025.parquet") |>
        filter(COICOP2018 == "00", NATURE == "INDICE", REF_AREA == "FE", FREQ == "A",
               MENAGES_IPC == "ENSEMBLE", PRIX_CONSO == "SO") |> mutate(indice = "IPC")
    ) |>
      filter(TIME_PERIOD %in% c("2017", "2023")) |>
      select(indice, TIME_PERIOD, OBS_VALUE) |>
      pivot_wider(names_from = TIME_PERIOD, values_from = OBS_VALUE) |>
      transmute(prix_2017_2023 = indice, deflateur = `2023` / `2017`),
    by = "prix_2017_2023") |>
  mutate(centile = c(D1 = 10, Q1 = 25, D5 = 50, Q3 = 75, D9 = 90, C95 = 95, C99 = 99)[indicateur],
         evolution = 100 * (s2023 / (s2017 * deflateur) - 1),
         prix_2017_2023 = paste0("Déflateur : ", prix_2017_2023)) |>
  ggplot(aes(x = centile, y = evolution, color = prix_2017_2023)) +
  geom_hline(yintercept = 0, color = "grey40", linewidth = 0.4) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2.5) +
  geom_text(aes(label = format(round(evolution, 1), decimal.mark = ",")), vjust = -1, size = 3, show.legend = FALSE) +
  scale_color_manual(values = c("Déflateur : IPC" = bleu_clair, "Déflateur : IPCH" = bleu), name = NULL) +
  scale_x_continuous(breaks = c(10, 25, 50, 75, 90, 95, 99)) +
  scale_y_continuous(breaks = seq(-6, 6, 1), expand = expansion(mult = c(0.1, 0.15))) +
  theme_minimal(base_size = 12) +
  labs(x = "Centile de salaire net en EQTP", y = NULL,
       title = "Salaire net en EQTP par centile, évolution entre 2017 et 2023",
       subtitle = "en %, en euros constants, séries longues de l'Insee (champ constant)") +
  theme(legend.position = "bottom", panel.grid.minor = element_blank())

Par centile, 2017-2025 : séries longues prolongées

Il s’agit des mêmes séries longues, prolongées en 2024 et 2025 par les évolutions en euros constants à champ constant publiées dans l’IP2079 (2024/2023) et l’IP2132 (2025/2024). Ces publications ne donnent pas P25 et P75 : leur évolution est approchée par la moyenne de celles des déciles voisins (D2 et D3 pour P25, D7 et D8 pour P75). Ces deux points sont dessinés en creux.

Code
indices_prix <- bind_rows(
  read_parquet("IPCH-2025.parquet") |>
    filter(COICOP2018 == "00", NATURE == "INDICE", REF_AREA == "FE", FREQ == "A") |> mutate(indice = "IPCH"),
  read_parquet("IPC-2025.parquet") |>
    filter(COICOP2018 == "00", NATURE == "INDICE", REF_AREA == "FE", FREQ == "A",
           MENAGES_IPC == "ENSEMBLE", PRIX_CONSO == "SO") |> mutate(indice = "IPC")
) |>
  transmute(indice, annee = as.integer(TIME_PERIOD), p = as.numeric(OBS_VALUE)) |>
  pivot_wider(names_from = indice, values_from = p)
ratio <- function(x, a, b) x[indices_prix$annee == b] / x[indices_prix$annee == a]

# Évolution réelle (IPC) 2023 -> 2025 à champ constant, par quantile
evol_2023_2025 <- evol_quantiles_ip |>
  select(annee, indicateur, evol_constants) |>
  pivot_wider(names_from = annee, values_from = evol_constants, names_prefix = "e") |>
  mutate(facteur = (1 + e2024 / 100) * (1 + e2025 / 100)) |>
  select(indicateur, facteur)
evol_2023_2025 <- bind_rows(
  evol_2023_2025 |> filter(indicateur %in% c("D1", "D5", "D9", "C95", "C99")) |> mutate(approche = FALSE),
  tibble(indicateur = c("Q1", "Q3"),
         facteur = c(mean(evol_2023_2025$facteur[evol_2023_2025$indicateur %in% c("D2", "D3")]),
                     mean(evol_2023_2025$facteur[evol_2023_2025$indicateur %in% c("D7", "D8")])),
         approche = TRUE)
)

SLS2025_EQTP08 |>
  filter(sexe == "Ensemble", annee %in% c(2017, 2023),
         indicateur %in% c("D1", "Q1", "D5", "Q3", "D9", "C95", "C99")) |>
  pivot_wider(names_from = annee, values_from = value, names_prefix = "s") |>
  inner_join(evol_2023_2025, by = "indicateur") |>
  mutate(reel_ipc = (s2023 / s2017) / ratio(indices_prix$IPC, 2017, 2023) * facteur,
         IPC = 100 * (reel_ipc - 1),
         IPCH = 100 * (reel_ipc * ratio(indices_prix$IPC, 2017, 2025) / ratio(indices_prix$IPCH, 2017, 2025) - 1),
         centile = c(D1 = 10, Q1 = 25, D5 = 50, Q3 = 75, D9 = 90, C95 = 95, C99 = 99)[indicateur]) |>
  pivot_longer(c(IPC, IPCH), names_to = "deflateur", values_to = "evolution") |>
  mutate(deflateur = paste0("Déflateur : ", deflateur)) |>
  ggplot(aes(x = centile, y = evolution, color = deflateur)) +
  geom_hline(yintercept = 0, color = "grey40", linewidth = 0.4) +
  geom_line(linewidth = 1.1) +
  geom_point(aes(shape = approche), size = 2.5, stroke = 1.1, fill = "white") +
  geom_text(aes(label = format(round(evolution, 1), decimal.mark = ",")), vjust = -1, size = 3, show.legend = FALSE) +
  scale_color_manual(values = c("Déflateur : IPC" = bleu_clair, "Déflateur : IPCH" = bleu), name = NULL) +
  scale_shape_manual(values = c("FALSE" = 16, "TRUE" = 21), guide = "none") +
  scale_x_continuous(breaks = c(10, 25, 50, 75, 90, 95, 99)) +
  scale_y_continuous(breaks = seq(-6, 6, 1), expand = expansion(mult = c(0.1, 0.15))) +
  theme_minimal(base_size = 12) +
  labs(x = "Centile de salaire net en EQTP", y = NULL,
       title = "Salaire net en EQTP par centile, évolution entre 2017 et 2025",
       subtitle = "en %, en euros constants, séries longues de l'Insee prolongées (champ constant)") +
  theme(legend.position = "bottom", panel.grid.minor = element_blank())

Figure 4 : distribution par sexe des salaires mensuels nets en EQTP, en 2025

Niveaux

Code
fig4 |>
  filter(quantile != "Moyenne") |>
  mutate(quantile = fct_reorder(quantile, ordre),
         sexe = factor(sexe, levels = c("Femmes", "Hommes", "Ensemble"))) |>
  ggplot(aes(x = quantile, y = niveau, color = sexe, group = sexe)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  scale_color_manual(values = c("Femmes" = rouge, "Hommes" = bleu, "Ensemble" = "grey50"), name = NULL) +
  scale_y_log10(breaks = c(1500, 2000, 3000, 4000, 6000, 8000, 12000),
                labels = scales::label_number(big.mark = " ")) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = "Euros par mois",
       title = "Salaire mensuel net en EQTP par quantile et par sexe, en 2025",
       subtitle = "Échelle logarithmique") +
  theme(legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid.minor = element_blank())

Évolution 2025/2024, en euros constants

Code
fig4 |>
  mutate(quantile = fct_reorder(quantile, ordre),
         sexe = factor(sexe, levels = c("Femmes", "Hommes", "Ensemble"))) |>
  ggplot(aes(x = quantile, y = evol, fill = sexe)) +
  geom_col(position = position_dodge(width = 0.8), width = 0.75) +
  scale_fill_manual(values = c("Femmes" = rouge, "Hommes" = bleu, "Ensemble" = "grey60"), name = NULL) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Évolution 2025/2024 des salaires nets en EQTP, en euros constants",
       subtitle = "en %") +
  theme(legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank())

Figure 5 : évolution de la distribution des salaires nets en EQTP depuis 1996, en euros constants

Déciles, linéaire

Code
plot_fig5 <- fig5 |>
  filter(indicateur != "D9_D1") |>
  mutate(indicateur = recode(indicateur, D1 = "1er décile (D1)", Mediane = "Médiane", D9 = "9e décile (D9)")) |>
  ggplot(aes(x = annee, y = value, color = indicateur)) +
  geom_line(linewidth = 1.2) +
  geom_hline(yintercept = 100, color = "grey60", linetype = "dashed") +
  scale_color_manual(values = c("1er décile (D1)" = rouge, "Médiane" = "grey40", "9e décile (D9)" = bleu), name = NULL) +
  scale_x_continuous(breaks = seq(1996, 2026, 2)) +
  scale_y_continuous(breaks = seq(90, 130, 2)) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Déciles des salaires nets en EQTP, en euros constants",
       subtitle = "Indice base 100 en 1996 - Échelle linéaire") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank())

plot_fig5

Déciles, log

Code
plot_fig5 +
  scale_y_log10(breaks = seq(90, 130, 2)) +
  labs(subtitle = "Indice base 100 en 1996 - Échelle logarithmique")

Rapport interdécile D9/D1

Code
fig5 |>
  filter(indicateur == "D9_D1") |>
  ggplot(aes(x = annee, y = value)) +
  geom_line(color = bleu, linewidth = 1.2) +
  geom_point(color = bleu, size = 1.5) +
  scale_x_continuous(breaks = seq(1996, 2026, 2)) +
  scale_y_continuous(breaks = seq(2.6, 3.2, 0.05)) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Rapport interdécile D9/D1 des salaires nets en EQTP") +
  theme(panel.grid.minor = element_blank())

Tableau complémentaire 1 : salaires horaires moyens selon le sexe et la CS

Code
tc1 |>
  filter(sexe != "Ensemble", categorie != "Ensemble") |>
  mutate(categorie = recode(categorie, Employées = "Employés", Ouvrières = "Ouvriers"),
         categorie = fct_inorder(categorie)) |>
  ggplot(aes(x = categorie, y = net_2025, fill = sexe)) +
  geom_col(position = position_dodge(width = 0.8), width = 0.75) +
  geom_text(aes(label = net_2025), position = position_dodge(width = 0.8), vjust = -0.4, size = 3.5) +
  scale_fill_manual(values = c("Femmes" = rouge, "Hommes" = bleu), name = NULL) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = "Euros par heure",
       title = "Salaire horaire net moyen, en 2025",
       subtitle = "selon le sexe et la catégorie socioprofessionnelle") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank())

Code
tc1 |>
  print_table_conditional()
sexe categorie brut_2024 brut_2025 brut_evol_constants net_2024 net_2025 net_evol_constants
Femmes Ensemble 21.5 22.2 1.9 16.3 16.7 1.7
Femmes Cadres 36.7 37.6 1.5 27.1 27.8 1.4
Femmes Professions intermédiaires 21.2 21.6 1.2 16.0 16.3 1.1
Femmes Employées 16.2 16.5 1.3 12.5 12.8 1.2
Femmes Ouvrières 15.6 16.0 1.4 12.0 12.3 1.3
Hommes Ensemble 24.5 25.1 1.6 18.6 19.0 1.4
Hommes Cadres 42.8 43.7 1.1 31.9 32.5 1.0
Hommes Professions intermédiaires 23.5 24.0 1.2 17.9 18.2 1.0
Hommes Employés 16.1 16.5 1.1 12.6 12.9 1.0
Hommes Ouvriers 17.4 17.8 1.3 13.4 13.7 1.1
Ensemble Ensemble 23.2 23.9 1.7 17.6 18.1 1.6
Ensemble Cadres 40.5 41.3 1.2 30.1 30.7 1.1
Ensemble Professions intermédiaires 22.4 22.9 1.2 17.0 17.3 1.0
Ensemble Employés 16.2 16.5 1.2 12.5 12.8 1.1
Ensemble Ouvriers 17.1 17.4 1.3 13.2 13.4 1.1

Tableau complémentaire 2 : salaires mensuels moyens selon le sexe et la CS

Code
tc2 |>
  filter(sexe != "Ensemble", categorie != "Ensemble") |>
  mutate(categorie = recode(categorie, Employées = "Employés", Ouvrières = "Ouvriers"),
         categorie = fct_inorder(categorie)) |>
  ggplot(aes(x = categorie, y = net_2025, fill = sexe)) +
  geom_col(position = position_dodge(width = 0.8), width = 0.75) +
  geom_text(aes(label = format(net_2025, big.mark = " ")),
            position = position_dodge(width = 0.8), vjust = -0.4, size = 3.5) +
  scale_fill_manual(values = c("Femmes" = rouge, "Hommes" = bleu), name = NULL) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = "Euros par mois",
       title = "Salaire mensuel net moyen en EQTP, en 2025",
       subtitle = "selon le sexe et la catégorie socioprofessionnelle") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank())

Code
tc2 |>
  print_table_conditional()
sexe categorie brut_2024 brut_2025 brut_evol_constants net_2024 net_2025 net_evol_constants part_effectifs_2024 part_effectifs_2025
Femmes Ensemble 3316 3410 1.9 2511 2578 1.7 42.0 42.0
Femmes Cadres 5621 5757 1.5 4162 4258 1.3 21.0 21.5
Femmes Professions intermédiaires 3262 3331 1.2 2465 2515 1.1 23.1 23.0
Femmes Employées 2493 2549 1.3 1927 1968 1.2 43.6 43.3
Femmes Ouvrières 2406 2463 1.4 1850 1890 1.3 12.3 12.1
Hommes Ensemble 3802 3898 1.6 2888 2956 1.4 58.0 58.0
Hommes Cadres 6569 6708 1.2 4896 4994 1.0 24.5 24.9
Hommes Professions intermédiaires 3660 3737 1.1 2778 2830 0.9 18.7 18.6
Hommes Employés 2509 2560 1.1 1963 2000 1.0 16.5 16.6
Hommes Ouvriers 2713 2773 1.3 2095 2137 1.1 40.3 39.8
Ensemble Ensemble 3598 3693 1.7 2729 2797 1.5 100.0 100.0
Ensemble Cadres 6206 6342 1.2 4615 4711 1.1 23.0 23.5
Ensemble Professions intermédiaires 3472 3545 1.1 2630 2681 1.0 20.5 20.5
Ensemble Employés 2499 2553 1.2 1939 1979 1.1 27.9 27.8
Ensemble Ouvriers 2657 2717 1.3 2050 2092 1.1 28.6 28.2

Tableau complémentaire 5 : prime de partage de la valeur (PPV)

Code
tc5 |>
  mutate(categorie = fct_inorder(categorie),
         annee = factor(annee)) |>
  ggplot(aes(x = categorie, y = part, fill = annee)) +
  geom_col(position = position_dodge(width = 0.8), width = 0.75) +
  geom_text(aes(label = part), position = position_dodge(width = 0.8), vjust = -0.4, size = 3.2) +
  scale_fill_manual(values = c("2024" = bleu_clair, "2025" = bleu), name = NULL) +
  theme_minimal(base_size = 12) +
  labs(x = NULL, y = NULL,
       title = "Part de salariés ayant perçu la PPV",
       subtitle = "en %") +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank())

Code
tc5 |>
  print_table_conditional()
categorie annee part montant
Femmes 2024 14.9 875
Femmes 2025 15.2 851
Hommes 2024 14.3 954
Hommes 2025 15.0 906
Cadres 2024 11.5 1223
Cadres 2025 13.1 1173
Professions intermédiaires 2024 17.3 895
Professions intermédiaires 2025 17.4 886
Employés 2024 14.1 831
Employés 2025 14.4 761
Ouvriers 2024 15.4 868
Ouvriers 2025 15.8 832
Ensemble 2024 14.6 918
Ensemble 2025 15.1 881