Curs d’Estadística amb R per a Metges - Capítol 2: Estadística Descriptiva

Author

P. Loma-Osorio

Published

October 10, 2026

Estadística descriptiva i exploració de dades

library(tidyverse)
library(readxl)
library(naniar)
library(janitor)

En aquest capítol aprendrem a importar dades clíniques, fer-hi un primer cop d’ull exhaustiu, gestionar valors absents, netejar i ordenar variables i descriure les dades tant de forma univariada com bivariada. L’estadística descriptiva i l’exploració de dades (Exploratory Data Analysis, EDA) és el primer pas imprescindible de qualsevol projecte de recerca biomèdica.

El decàleg de l’anàlisi de dades en recerca clínica
  1. Importar i inspeccionar:
    • Llegir dades des d’un format extern (read_csv(), read_excel(), read_sav()).
    • Verificar estructura, noms de columnes i dimensions.
    • Auditar sistemàticament els valors perduts (missing data) i inconsistències.
    • Eines clau: glimpse(), summary(), naniar.
  2. Seleccionar, transformar i ordenar:
    • Triar les columnes rellevants de la cohort (select()).
    • Recodificar i netejar variables (mutate(), case_when(), factor()).
    • Reordenar files per criteris clínics (arrange()).
  3. Descriure les dades univariadament:
    • Variables quantitatives: mesurar tendència central i dispersió segons la forma de la distribució (simetria/normalitat vs. asimetria).
    • Variables qualitatives: taules de freqüència i proporcions.
  4. Relacionar i sintetitzar (Bivariada i Taula 1):
    • Taules de contingència, risc relatiu (RR) i odds ratio (OR).
    • Correlació entre paràmetres continus.
    • Construcció automatitzada de la taula basal de característiques de l’estudi (compareGroups, gtsummary).

1. Importació i inspecció inicial de la cohort

Per als exemples pràctics d’aquest capítol farem servir una base de dades històrica universalment coneguda en ciència de dades: la cohort de passatgers del Titanic. Permet il·lustrar clarament variables contínues (edat, tarifa), factors binaris (supervivència, sexe) i categories ordinals (classe del bitllet).

# Si descarregueu el fitxer dins de la carpeta 'data/':
titanic <- read_csv("data/titanic.csv")

Un cop carregat el dataframe, el primer pas no és mai calcular mitjanes a cegues, sinó inspeccionar-ne l’arquitectura:

class(titanic)
[1] "tbl_df"     "tbl"        "data.frame"
glimpse(titanic)
Rows: 200
Columns: 12
$ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,…
$ Survived    <dbl> 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1…
$ Pclass      <int> 2, 3, 3, 3, 1, 2, 3, 3, 3, 2, 1, 3, 3, 3, 1, 1, 1, 2, 3, 2…
$ Name        <chr> "Passenger 1", "Passenger 2", "Passenger 3", "Passenger 4"…
$ Sex         <chr> "female", "female", "male", "male", "male", "male", "femal…
$ Age         <dbl> 11, 26, 18, 30, 47, 31, 36, 24, 21, 36, 13, 17, 52, 33, 12…
$ SibSp       <int> 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0…
$ Parch       <int> 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 0, 1, 0…
$ Ticket      <chr> "T1000", "T1001", "T1002", "T1003", "T1004", "T1005", "T10…
$ Fare        <dbl> 37.51, 45.23, 9.35, 31.29, 7.88, 10.91, 27.01, 62.50, 43.8…
$ Cabin       <chr> "C123", NA, "C85", NA, "C85", "C85", "C85", NA, "C85", NA,…
$ Embarked    <chr> "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"…

La comanda glimpse() de dplyr ofereix una radiografia compacta: informa del nombre de files (observacions/pacients), columnes (variables), el tipus de dada informàtic de cadascuna i les primeres entrades.

Funcions clàssiques d’inspecció a R base

Al costat de glimpse(), disposem d’un ventall de funcions d’R base molt útils per a la consola:

names(titanic)       # Llista els noms de totes les variables
head(titanic, n = 5) # Mostra les primeres 5 files
tail(titanic, n = 5) # Mostra les darreres 5 files
summary(titanic)     # Resum matemàtic automàtic de cada columna

2. Auditoria de valors perduts (Missing Data)

A la recerca clínica real, una base de dades sense valors perduts (NA, Not Available) és pràcticament inexistent. Abans de començar qualsevol anàlisi, cal saber on són, quants en tenim i com es distribueixen.

Amb R base podem fer un recompte ràpid per columnes:

colSums(is.na(titanic))
PassengerId    Survived      Pclass        Name         Sex         Age 
          0           0           0           0           0           0 
      SibSp       Parch      Ticket        Fare       Cabin    Embarked 
          0           0           0           0         135           0 

Anàlisi visual i tabular de missings amb naniar

El paquet naniar proporciona eines dissenyades específicament per avaluar patrons de valors perduts:

# Resum tabular percentual de valors absents per variable
miss_var_summary(titanic)
# A tibble: 12 × 3
   variable    n_miss pct_miss
   <chr>        <int>    <num>
 1 Cabin          135     67.5
 2 PassengerId      0      0  
 3 Survived         0      0  
 4 Pclass           0      0  
 5 Name             0      0  
 6 Sex              0      0  
 7 Age              0      0  
 8 SibSp            0      0  
 9 Parch            0      0  
10 Ticket           0      0  
11 Fare             0      0  
12 Embarked         0      0  
# Visualització de la matriu de valors perduts
vis_miss(titanic)

El comportament de NA a les funcions d’R

A R, qualsevol operació matemàtica aplicada sobre un vector que contingui ni que sigui un sol NA retornarà NA per seguretat.

mean(c(10, 20, NA))  # Retorna: NA

Per indicar-li que calculi la mesura sobre els casos vàlids existents, cal afegir sempre l’argument na.rm = TRUE (NA remove):

mean(c(10, 20, NA), na.rm = TRUE)  # Retorna: 15

3. Tipus de variables biomèdiques vs. Tipus de dades a R

Cal no confondre el concepte metodològic de tipus de variable amb el concepte informàtic de com es guarda a la memòria de l’ordinador.

Classificació metodològica de les variables

  1. Variables qualitatives o categòriques: Expressen atributs, qualitats o categories no mètriques.
    • Dicotòmiques o binàries: Només tenen dues opcions (p. ex., Mort intrahospitalària: Sí / No; Sexe: Home / Dona).
    • Nominals: Més de dues categories sense ordre jeràrquic intern (p. ex., Grup sanguini: A, B, AB, 0; Port d’embarcament: Cherbourg, Queenstown, Southampton).
    • Ordinals: Categories amb un ordre natural o escala de gravetat (p. ex., Classe funcional NYHA: I, II, III, IV; Grau de dolor: Lleu, Moderat, Intens; Classe del bitllet: 1a, 2a, 3a).
  2. Variables quantitatives o numèriques: Representen magnituds mesurables numèricament.
    • Discretes: Valors enters fruit d’un recompte (p. ex., Nombre d’ingressos previs per insuficiència cardíaca: 0, 1, 2, 3…; Nombre de germans a bord: SibSp).
    • Contínues: Poden prendre qualsevol valor dins d’un interval continu (p. ex., Edat: 45.3 anys; Pressió arterial sistòlica: 138.5 mmHg; Troponina I: 234.8 ng/L).
Correspondència a R: character, numeric i factor
  • Un nombre es guarda com a numeric (double o integer).
  • Un text lliure es guarda com a character.
  • A R, les variables categòriques s’han de definir com a factor. Els factors permeten a R fixar els nivells possibles (levels), establir l’ordre jeràrquic (ordinals) i assignar automàticament etiquetes clíniques a taules i gràfics.

4. Manipulació fonamental: seleccionar, rebatejar i ordenar amb dplyr

Un cop entesa l’estructura, el flux habitual de treball consisteix a aïllar les variables d’interès i organitzar la cohort. La llibreria dplyr permet encadenar aquestes accions mitjançant l’operador de canonada %>% (o |>):

Seleccionar columnes: select()

Permet quedar-nos exclusivament amb les variables que formaran part de l’estudi:

sel_titanic <- titanic %>% 
  select(PassengerId, Age, Sex, Fare, Survived, Pclass)

head(sel_titanic, n = 3)
# A tibble: 3 × 6
  PassengerId   Age Sex     Fare Survived Pclass
        <int> <dbl> <chr>  <dbl>    <dbl>  <int>
1           1    11 female 37.5         0      2
2           2    26 female 45.2         0      3
3           3    18 male    9.35        0      3

Podem utilitzar criteris auxiliars de selecció:

df %>% select(-PassengerId)            # Exclou aquesta columna
df %>% select(starts_with("lab_"))     # Totes les que comencin per "lab_"
df %>% select(where(is.numeric))       # Només les columnes numèriques

Rebatejar variables: rename()

Assigna noms clínics nets i estandarditzats per evitar espais o caràcters confusos:

sel_titanic <- sel_titanic %>% 
  rename(classe = Pclass)

# O convertir tots els noms a minúscules d'un sol cop:
sel_titanic <- sel_titanic %>% 
  rename_with(tolower)

head(sel_titanic, n = 3)
# A tibble: 3 × 6
  passengerid   age sex     fare survived classe
        <int> <dbl> <chr>  <dbl>    <dbl>  <int>
1           1    11 female 37.5         0      2
2           2    26 female 45.2         0      3
3           3    18 male    9.35        0      3

Ordenar observacions: arrange()

Ordena les files segons una o més variables (per defecte de forma ascendent; amb desc() de forma descendent):

# Ordenem els passatgers del bitllet més car al més barat
sel_titanic_ordenat <- sel_titanic %>% 
  arrange(desc(fare))

head(sel_titanic_ordenat, n = 3)
# A tibble: 3 × 6
  passengerid   age sex     fare survived classe
        <int> <dbl> <chr>  <dbl>    <dbl>  <int>
1         172    24 female  209.        0      1
2          63    20 male    188.        0      1
3          32    36 male    165.        0      3
# Ordenació jeràrquica: primer per edat ascendent, i en cas d'empat pel preu descendent
sel_titanic_doble <- sel_titanic %>% 
  arrange(age, desc(fare))

head(sel_titanic_doble, n = 3)
# A tibble: 3 × 6
  passengerid   age sex      fare survived classe
        <int> <dbl> <chr>   <dbl>    <dbl>  <int>
1         198    -4 female  54.0         0      3
2         178     3 female   8.09        1      1
3          95     5 female 108.          1      2

5. Descriptiva de variables quantitatives

En enfrontar-nos a una variable numèrica contínua (com l’edat o el preu del bitllet), l’objectiu és descriure resumidament el seu comportament: on se centren les dades, com d’escampades estan i quina forma té la seva distribució.

Representació gràfica exploratòria: Boxplot i Histograma

Abans de calcular cap xifra, cal mirar la forma de les dades. Les dues eines clàssiques immediates a R base són el diagrama de caixa (boxplot) i l’histograma:

1. El diagrama de caixa (Boxplot)

Resumeix visualment la distribució mitjançant cinc paràmetres clau (els “cinc números de Tukey”): - La línia central gruixuda representa la mediana (percentil 50). - La caixa delimita el 50% central de les observacions, entre el primer quartil (\(Q_1\), percentil 25) i el tercer quartil (\(Q_3\), percentil 75). L’alçada de la caixa és el rang interquartílic (RIC): \(RIC = Q_3 - Q_1\). - Els “bigotis” s’estenen fins al valor més llunyà que no superi \(1.5 \times RIC\) des dels extrems de la caixa. - Els punts aïllats més enllà dels bigotis són considerats potencials valors atípics o extrems (outliers).

boxplot(titanic$Age, 
        horizontal = TRUE, 
        col = "#90CAF9", 
        main = "Diagrama de caixa de l'edat (Age)",
        xlab = "Edat (anys)")

2. L’histograma

Agrupa les dades en intervals consecutius (bins) i compta quantes observacions cauen a cadascun. R calcula el nombre òptim d’intervals per defecte mitjançant la fórmula d’Sturges (\(k = 1 + \log_2(n)\)):

hist(titanic$Age, 
     col = "#E3F2FD", 
     border = "#1565C0",
     main = "Histograma de l'edat", 
     xlab = "Edat (anys)", 
     ylab = "Freqüència absoluta (nombre de passatgers)")

Si només necessitem els talls numèrics de l’histograma sense generar el gràfic, podem fer servir l’argument plot = FALSE:

hist(titanic$Age, plot = FALSE)
$breaks
 [1] -10   0  10  20  30  40  50  60  70  80

$counts
[1]  1 18 39 37 56 34 13  1  1

$density
[1] 0.0005 0.0090 0.0195 0.0185 0.0280 0.0170 0.0065 0.0005 0.0005

$mids
[1] -5  5 15 25 35 45 55 65 75

$xname
[1] "titanic$Age"

$equidist
[1] TRUE

attr(,"class")
[1] "histogram"

Mètriques per descriure dades quantitatives

1. Tendència central (“On s’acumulen les dades?”)

  • Mitjana aritmètica (mean()): Suma de tots els valors dividida pel nombre total de subjectes. És altament sensible a valors extrems (outliers).
  • Mediana (median()): El valor central quan totes les dades s’ordenen de menor a major (percentil 50). És una mesura robusta, que no es deixa arrossegar per valors anòmals o asimetries severes.
  • Moda: El valor que es repeteix amb major freqüència. Té poc ús en variables contínues, però és útil si la distribució presenta més d’un pic (bimodalitat).

2. Dispersió (“Com d’escampats estan els valors?”)

  • Rang (range()): Diferència entre el valor màxim i el mínim.
  • Desviació estàndard (DE / SD, sd()): Mesura de dispersió mitjana respecte a la mitjana aritmètica. Té les mateixes unitats de mesura que la variable original.
  • Variança (var()): Quadrat de la desviació estàndard (\(s^2\)). Útil en càlculs matemàtics i models lineals, però difícil d’interpretar clínicament directament.
  • Rang interquartílic (RIC / IQR, IQR()): Diferència entre el tercer i el primer quartil (\(Q_3 - Q_1\)). Representa la dispersió del nucli central del 50% dels pacients.

3. Posició i percentils (quantile())

Els percentils divideixen la mostra ordenada en cent parts iguals: - \(Q_1\) (percentil 25): El 25% dels pacients tenen un valor igual o inferior. - \(Q_2\) (percentil 50): La mediana. - \(Q_3\) (percentil 75): El 75% dels pacients tenen un valor igual o inferior.

4. Forma de la distribució: Asimetria (Skewness) i Curtosi

  • Asimetria (Skewness): Indica si la distribució s’allarga cap a la dreta (asimetria positiva / cua llarga a valors alts, com la troponina o la despesa sanitària) o cap a l’esquerra (asimetria negativa).
  • Curtosi: Reflecteix la concentració de dades al voltant del pic central i el gruix de les cues en comparació amb una distribució gaussiana teòrica.
# Càlcul individual d'estadístics a R base (recordant sempre na.rm = TRUE)
min(titanic$Age, na.rm = TRUE)
[1] -4
max(titanic$Age, na.rm = TRUE)
[1] 75
range(titanic$Age, na.rm = TRUE)
[1] -4 75
mean(titanic$Age, na.rm = TRUE)
[1] 30.16
median(titanic$Age, na.rm = TRUE)
[1] 31
sd(titanic$Age, na.rm = TRUE)
[1] 14.05783
IQR(titanic$Age, na.rm = TRUE)
[1] 22
# Càlcul de diversos percentils simultàniament:
quantile(titanic$Age, probs = c(0.05, 0.25, 0.50, 0.75, 0.95), na.rm = TRUE)
 5% 25% 50% 75% 95% 
  9  18  31  40  53 
Resum complet automàtic d’un sol cop

Si voleu calcular mitjana, desviació, mediana, asimetria i curtosi de cop per a totes les columnes numèriques, la funció describe() del paquet psych és una de les eines més pràctiques d’R:

psych::describe(titanic$Age)

El criteri clínic de normalitat: Quan fer servir mitjana \(\pm\) DE o mediana [RIC]?

En medicina, una de les decisions metodològiques més habituals és decidir com presentar les variables numèriques a la Taula 1 d’un article o comunicació:

  1. Variables simètriques (distribució normal o quasi-normal):
    • La mitjana i la mediana coincideixen pràcticament en el mateix punt central.
    • La forma de l’histograma s’assembla a la campana de Gauss.
    • Exemples clínics típics: Edat, pressió arterial sistòlica o diastòlica, alçada.
    • Indicador escollit: Mitjana \(\pm\) Desviació Estàndard (DE).
  2. Variables asimètriques o amb valors extrems:
    • La mitjana es veu arrossegada pels valors molt alts o molt baixos, donant una falsa impressió del pacient “típic”.
    • Exemples clínics típics: Biomarcadors cardíacs (troponina, NT-proBNP), temps d’espera, dies d’estada a la Unitat Coronària / UCI, preu del bitllet (Fare).
    • Indicador escollit: Mediana [Rang Interquartílic, \(Q_1\) - \(Q_3\)].
par(mfrow = c(1, 2))

# 1. Distribució simètrica (Edat)
hist(titanic$Age, 
     main = "Simètrica: Mitjana +/- DE", 
     xlab = "Edat", col = "#C8E6C9", border = "white", probability = TRUE)
lines(density(titanic$Age, na.rm = TRUE), col = "#2E7D32", lwd = 2)

# 2. Distribució asimètrica positiva severa (Fare)
hist(titanic$Fare, 
     main = "Asimètrica: Mediana [RIC]", 
     xlab = "Preu bitllet (Fare)", col = "#FFCCBC", border = "white", probability = TRUE)
lines(density(titanic$Fare, na.rm = TRUE), col = "#D84315", lwd = 2)

par(mfrow = c(1, 1))

6. Descriptiva de variables qualitatives (categòriques)

A R tenim dos tipus bàsics de dades per guardar etiquetes: - character (text): Cadena alfanumèrica oberta (per exemple: un codi d’identificació personal o CIP, comentaris lliures). No té estructura interna de categories. - factor: Estructura que conté una llista tancada i predefinida de valors possibles (levels). Permet establir ordres jeràrquics (ordinals) i és el format requerit pels models d’anàlisi estadística i generació de taules.

# Convertim la variable Sex i Survived a factor amb etiquetes comprensibles
titanic <- titanic %>% 
  mutate(
    sexe = factor(Sex, levels = c("male", "female"), labels = c("Home", "Dona")),
    supervivent = factor(Survived, levels = c(0, 1), labels = c("Mort", "Viu"))
  )

Freqüències absolutes i proporcions amb R base

La taula és el mètode clàssic per descriure variables qualitatives: - Freqüència absoluta (\(n\)): Nombre de casos comptabilitzats en cada categoria (table()). - Freqüència relativa o proporció (\(\%\)): Pes proporcional de cada categoria respecte al total observat (prop.table()).

taula_sexe <- table(titanic$sexe)
taula_sexe

Home Dona 
  84  116 
# Proporcions sobre 1:
prop.table(taula_sexe)

Home Dona 
0.42 0.58 
# Percentatge arrodonit a un decimal:
round(prop.table(taula_sexe) * 100, 1)

Home Dona 
  42   58 

Gràfics bàsics per a variables qualitatives: Barres vs. Pastís

Tot i la popularitat històrica dels gràfics de sectors (pie charts), en comunicació científica rigorosa es desaconsellen àmpliament perquè l’ull humà no percep amb precisió les àrees o angles relatius:

par(mfrow = c(1, 2))

# Gràfic de barres (l'opció clara i recomanada)
barplot(taula_sexe, 
        col = c("#64B5F6", "#F06292"), 
        main = "Gràfic de barres (recomanat)", 
        ylab = "Nombre de passatgers")

# Gràfic de pastís (menys recomanat per comparacions precises)
pie(taula_sexe, 
    col = c("#64B5F6", "#F06292"), 
    main = "Gràfic de pastís")

par(mfrow = c(1, 1))

La forma moderna i directa: el paquet janitor

Per evitar haver de combinar manualment table(), prop.table() i càlculs de totals, la llibreria janitor ofereix la funció tabyl(), que produeix taules de freqüència completes i elegants en una sola instrucció:

titanic %>% 
  tabyl(sexe) %>% 
  adorn_percentages("col") %>% 
  adorn_pct_formatting(digits = 1) %>% 
  adorn_totals("row")
  sexe   n percent
  Home 0.4   42.0%
  Dona 0.6   58.0%
 Total 1.0       -

7. Estadística descriptiva bivariada: Relacionant variables

Mentre que l’anàlisi univariada descriu com es comporta cada variable aïlladament, l’anàlisi bivariada té una intenció diferent: explorar la relació entre dues variables per generar o recolzar hipòtesis clíniques.

Relació entre dues variables qualitatives: Taules de contingència

Quan analitzem dues variables categòriques (per exemple, classe del passatge i supervivència), creuem les seves freqüències en una taula bidimensional:

taula_contingencia <- table(titanic$supervivent, titanic$Pclass)
dimnames(taula_contingencia)[[2]] <- c("1a Classe", "2a Classe", "3a Classe")
taula_contingencia
      
       1a Classe 2a Classe 3a Classe
  Mort        20        40        50
  Viu         27        24        39

Les freqüències absolutes solen ser difícils de comparar directament si les mostres per grup no són idèntiques. Per això calculem proporcions relatives condicionades:

# Proporcions condicionades per columnes (margin = 2):
# Dels passatgers de cada classe, quin percentatge va morir o sobreviure?
round(prop.table(taula_contingencia, margin = 2) * 100, 1)
      
       1a Classe 2a Classe 3a Classe
  Mort      42.6      62.5      56.2
  Viu       57.4      37.5      43.8

Visualització: Barplot agrupat i Gràfic de mosaic

Per inspeccionar aquesta associació gràficament, el diagrama de barres adossades o el gràfic de mosaic permeten valorar visualment si la distribució canvia entre grups:

par(mfrow = c(1, 2))

# Barplot adossat
barplot(taula_contingencia, 
        beside = TRUE, 
        legend.text = TRUE, 
        col = c("#EF9A9A", "#A5D6A7"),
        main = "Supervivència per classe",
        ylab = "Nombre de passatgers")

# Gràfic de mosaic (l'amplada de les columnes reflecteix el pes de cada grup)
mosaicplot(t(taula_contingencia), 
           col = c("#EF9A9A", "#A5D6A7"), 
           main = "Gràfic de mosaic")

par(mfrow = c(1, 1))

Taules creuades ràpides amb janitor

Amb el paquet janitor podem obtenir els recomptes, percentatges per columnes i totals marginals d’un sol cop:

titanic %>% 
  tabyl(supervivent, Pclass) %>% 
  adorn_percentages("col") %>% 
  adorn_pct_formatting(digits = 1) %>% 
  adorn_ns(position = "front")
 supervivent          1          2          3
        Mort 20 (42.6%) 40 (62.5%) 50 (56.2%)
         Viu 27 (57.4%) 24 (37.5%) 39 (43.8%)

Mesures d’associació clínica: Odds Ratio (OR) i Risk Ratio (RR)

Davant d’una taula \(2 \times 2\) entre una exposició i un esdeveniment clínic, resumim la força de la relació mitjançant el Risk Ratio (RR) o l’Odds Ratio (OR).

Construïm la taula creuant el sexe amb el fet de sobreviure:

taula_2x2 <- table(titanic$sexe, titanic$supervivent)
taula_2x2
      
       Mort Viu
  Home   35  49
  Dona   75  41
Conceptes epidemiològics clau: OR vs. RR
  • Risk Ratio (RR / Risc Relatiu): És la raó entre el risc d’esdeveniment en el grup exposat respecte al no exposat. És la mesura intuïtiva pròpia dels estudis de cohorts i dels assajos clínics controlats: \[RR = \frac{a / (a + b)}{c / (c + d)}\]
  • Odds Ratio (OR / Raó de momis): És la raó entre la odds (raó d’esdevenir-se vs. no esdevenir-se) en el grup exposat respecte al no exposat. S’utilitza obligatòriament en estudis de casos i controls, ja que no podem conèixer la incidència poblacional real. En esdeveniments freqüents (>10%), l’OR sobreestima l’associació respecte al RR: \[OR = \frac{a \times d}{b \times c}\]

El paquet epitools permet calcular tant les estimacions puntuals com els seus intervals de confiança del 95%:

library(epitools)

# Càlcul d'Odds Ratio i Risk Ratio
oddsratio(taula_2x2)$measure
      odds ratio with 95% C.I.
        estimate     lower     upper
  Home 1.0000000        NA        NA
  Dona 0.3929968 0.2186067 0.6978925
riskratio(taula_2x2)$measure
      risk ratio with 95% C.I.
        estimate     lower     upper
  Home 1.0000000        NA        NA
  Dona 0.6059113 0.4464715 0.8222889

Relació entre dues variables quantitatives: Correlació de Pearson

Quan volem avaluar com varien conjuntament dues variables contínues, utilitzem el coeficient de correlació lineal de Pearson (\(r\)): - Varia entre \(-1\) i \(+1\). - \(r = 0\): absència d’associació lineal. - \(r = +1\): relació lineal positiva perfecta (quan una variable puja, l’altra també). - \(r = -1\): relació lineal inversa perfecta.

# Exemple amb el conjunt de dades iris: longitud vs. amplada de pètals
cor_valor <- cor(iris$Petal.Length, iris$Sepal.Length)
round(cor_valor, 2)
[1] 0.87
# Gràfic de dispersió amb línia de tendència
plot(iris$Sepal.Length, iris$Petal.Length,
     xlab = "Longitud del sèpal (cm)",
     ylab = "Longitud del pètal (cm)",
     main = "Diagrama de dispersió i correlació lineal",
     pch = 19, col = "#1976D2")
abline(lm(Petal.Length ~ Sepal.Length, data = iris), col = "#D32F2F", lwd = 2)

Correlació no implica causalitat ni relació lineal

El coeficient de Pearson només quantifica dependències estrictament lineals. Si dues variables tenen una relació en forma de “U” o corba parabòlica, \(r\) pot sortir proper a zero tot i existir una forta associació biològica. Sempre s’ha de visualitzar el núvol de punts abans d’interpretar un coeficient de correlació.


8. Manipulació avançada amb dplyr: Agrupar, resumir i recodificar

Per aprofundir en la descripció bivariada, la combinació de group_by() i summarise() permet calcular descriptius per subgrups clínics de forma neta i llegible.

Agrupar i resumir: group_by() i summarise()

# Calculem l'edat mitjana i la tarifa mitjana segons el sexe i la supervivència
titanic %>% 
  filter(!is.na(Age)) %>% 
  group_by(sexe, supervivent) %>% 
  summarise(
    n_pacients = n(),
    edat_mitjana = round(mean(Age), 1),
    tarifa_mediana = round(median(Fare), 1),
    .groups = "drop"
  )
# A tibble: 4 × 5
  sexe  supervivent n_pacients edat_mitjana tarifa_mediana
  <fct> <fct>            <int>        <dbl>          <dbl>
1 Home  Mort                35         27.7           31.8
2 Home  Viu                 49         30             37  
3 Dona  Mort                75         30.8           30.7
4 Dona  Viu                 41         31.2           26.1

Recodificació de variables contínues a categories: mutate() i case_when()

A la pràctica mèdica sovint necessitem estratificar un paràmetre continu en intervals de risc clínic. La funció case_when() és l’eina més robusta i intuïtiva per fer-ho:

titanic_recodificat <- titanic %>% 
  mutate(
    grup_edat = case_when(
      Age < 18 ~ "Pediàtric (<18)",
      Age >= 18 & Age < 65 ~ "Adult (18-64)",
      Age >= 65 ~ "Geriàtric (>=65)",
      TRUE ~ "Desconegut / NA"
    ),
    categoria_tarifa = cut(
      Fare, 
      breaks = c(0, 15, 50, Inf), 
      labels = c("Baixa", "Mitjana", "Alta"),
      include.lowest = TRUE
    )
  )

table(titanic_recodificat$grup_edat)

   Adult (18-64) Geriàtric (>=65)  Pediàtric (<18) 
             152                1               47 
table(titanic_recodificat$categoria_tarifa)

  Baixa Mitjana    Alta 
     49      93      58 

9. La “Taula 1” automatitzada: compareGroups i gtsummary

Generar a mà la taula de característiques basals d’un estudi mèdic és una font habitual d’errors de transcripció. Disposem de dos paquets capdavanters a la literatura biomèdica per automatitzar-ho:

Opció A: El paquet compareGroups

Molt utilitzat en epidemiologia cardiovascular i recerca clínica a Catalunya i Espanya. Permet generar taules amb estadístics adequats per tipus de variable i exportar-les directament a Word, Excel o PDF:

library(compareGroups)

# Definim la fórmula d'estratificació (supervivent segons la resta de variables)
taula_cg <- compareGroups(supervivent ~ Age + sexe + Pclass + Fare, data = titanic)
resultat_taula <- createTable(taula_cg, show.all = TRUE)
resultat_taula

--------Summary descriptives table by 'supervivent'---------

______________________________________________________ 
            [ALL]       Mort         Viu     p.overall 
            N=200       N=110       N=90               
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
Age      30.2 (14.1) 29.8 (13.4) 30.6 (14.9)   0.708   
sexe:                                          0.002   
    Home 84 (42.0%)  35 (31.8%)  49 (54.4%)            
    Dona 116 (58.0%) 75 (68.2%)  41 (45.6%)            
Pclass   2.21 (0.80) 2.27 (0.75) 2.13 (0.85)   0.227   
Fare     41.2 (34.3) 42.1 (38.1) 40.1 (29.2)   0.673   
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 

Opció B: El paquet gtsummary

Una de les eines més modernes i elegants de l’entorn R per generar taules basals llestes per a publicació:

library(gtsummary)

titanic %>% 
  select(supervivent, Age, sexe, Pclass, Fare) %>% 
  tbl_summary(
    by = supervivent,
    statistic = list(
      Age ~ "{mean} ({sd})",
      Fare ~ "{median} ({p25}, {p75})",
      all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      Age ~ "Edat (anys)",
      sexe ~ "Sexe",
      Pclass ~ "Classe de passatge",
      Fare ~ "Tarifa del bitllet ($)"
    ),
    missing = "no"
  ) %>% 
  add_overall() %>% 
  bold_labels()
Characteristic Overall
N = 2001
Mort
N = 1101
Viu
N = 901
Edat (anys) 30 (14) 30 (13) 31 (15)
Sexe


    Home 84 (42%) 35 (32%) 49 (54%)
    Dona 116 (58%) 75 (68%) 41 (46%)
Classe de passatge


    1 47 (24%) 20 (18%) 27 (30%)
    2 64 (32%) 40 (36%) 24 (27%)
    3 89 (45%) 50 (45%) 39 (43%)
Tarifa del bitllet ($) 32 (15, 54) 32 (16, 52) 33 (14, 55)
1 Mean (SD); n (%); Median (Q1, Q3)

10. Anàlisi Exploratòria Automàtica de Dades (EDA): skimr i DataExplorer

Quan ens arriba una base de dades clínica amb desenes de columnes que no coneixem, fer resums variable per variable pot resultar lent. Hi ha llibreries pensades per fer una radiografia global immediata:

skimr: Resum estadístic compacte a la consola

Ofereix recomptes de valors perduts, percentils i mini-histogrames per a cada variable directament des d’R:

library(skimr)

# Resum ràpid de les columnes numèriques del Titanic
titanic %>% 
  select(Age, Fare, Survived) %>% 
  skim()
Data summary
Name Piped data
Number of rows 200
Number of columns 3
_______________________
Column type frequency:
numeric 3
________________________
Group variables None

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Age 0 1 30.16 14.06 -4 18.00 31.00 40.00 75.00 ▂▆▇▃▁
Fare 0 1 41.16 34.29 7 15.26 32.37 54.05 208.84 ▇▂▁▁▁
Survived 0 1 0.45 0.50 0 0.00 0.00 1.00 1.00 ▇▁▁▁▆

DataExplorer: Informes visuals complets

Permet avaluar l’arquitectura global de les dades, la matriu de valors perduts i les densitats amb instruccions mínimes:

library(DataExplorer)

# Proporció de tipus de dades i completitud global
plot_intro(titanic)

# Visualització del percentatge de dades absents
plot_missing(titanic)

Creació automàtica d’un informe HTML complet

Executant l’ordre create_report(titanic) a la consola, DataExplorer analitzarà tot el conjunt de dades i generarà un document HTML interactiu complet amb gràfics de correlació, distribucions i alertes de dades.


11. Introducció a la visualització avançada amb ggplot2

Mentre que els gràfics d’R base són útils per a la inspecció ràpida a la consola, el paquet ggplot2 (basat en la “Gramàtica dels Gràfics”) és el patró or per a figures destinades a publicacions mèdiques d’alt impacte.

Un gràfic de ggplot2 es construeix superposant capes: 1. Dades (data): La taula de dades d’origen. 2. Estètica (aes): Quines variables representen l’eix X, l’eix Y, el color o la forma. 3. Geometries (geom_*): Com es dibuixen aquestes dades (punts, barres, caixes, línies). 4. Temes (theme_*): L’aspecte visual general de la figura.

# Diagrama de dispersió elegant per espècies
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  geom_point(size = 2.5, alpha = 0.8) +
  theme_minimal() +
  labs(
    title = "Morfologia del sèpal per espècie",
    x = "Longitud del sèpal (cm)",
    y = "Amplada del sèpal (cm)",
    color = "Espècie"
  )

# Densitat de la tarifa per supervivència amb transparència
ggplot(titanic, aes(x = Fare, fill = supervivent)) +
  geom_density(alpha = 0.4) +
  xlim(0, 150) +
  theme_bw() +
  labs(
    title = "Distribució de la tarifa segons supervivència",
    x = "Tarifa ($)",
    y = "Densitat",
    fill = "Estat"
  )


install.packages(“compareGroups”)12. Exercicis pràctics del Capítol 2

Posa en pràctica tot el ventall d’eines d’inspecció, neteja i descriptiva treballades:

  1. Crea un script o document Quarto per resoldre els exercicis i organitza el teu codi amb comentaris clars.
  2. Instal·la (si cal) el paquet medicaldata i carrega un dels seus conjunts de dades clíniques (per exemple, opt).
  3. Fes una inspecció estructural completa de les dades del Titanic, incloent l’auditoria de valors absents amb naniar.
  4. Identifica i filtra els passatgers que representen valors atípics (outliers) a l’edat del Titanic i torna a representar el boxplot.
  5. Compara el preu del bitllet (Fare) segons el sexe de forma gràfica utilitzant un diagrama de caixes.
  6. Fes un histograma de l’alçada dels personatges del conjunt de dades starwars (inclòs a dplyr).
  7. Selecciona les columnes de nom i massa (mass) de starwars i ordena-les per esbrinar quin personatge és el més pesat.
  8. Compara numèricament (mitjana o mediana) i gràficament el pes dels personatges d’Starwars segons el seu gènere.
  9. Inspecciona el dataset iris, obtén la llista de variables i el seu resum descriptiu.
  10. Construeix una figura d’alta resolució amb ggplot2 a partir de les dades d’iris mostrant la distribució de longituds de pètal per espècie.
  11. Genera una Taula 1 automatitzada amb compareGroups o gtsummary estratificant per supervivència a la base de dades del Titanic.

library(tidyverse)
library(naniar)
library(janitor)
library(gtsummary)

# Exercici 2: medicaldata (opcional/instal·lació)
# install.packages("medicaldata")
# library(medicaldata)

# Exercici 3: Auditoria de missings al Titanic
miss_var_summary(titanic)
# A tibble: 14 × 3
   variable    n_miss pct_miss
   <chr>        <int>    <num>
 1 Cabin          135     67.5
 2 PassengerId      0      0  
 3 Survived         0      0  
 4 Pclass           0      0  
 5 Name             0      0  
 6 Sex              0      0  
 7 Age              0      0  
 8 SibSp            0      0  
 9 Parch            0      0  
10 Ticket           0      0  
11 Fare             0      0  
12 Embarked         0      0  
13 sexe             0      0  
14 supervivent      0      0  
vis_miss(titanic)

# Exercici 4: Detecció i exclusió d'outliers a l'edat
# Calculem els límits de Tukey per a l'edat
q_edat <- quantile(titanic$Age, probs = c(0.25, 0.75), na.rm = TRUE)
iqr_edat <- IQR(titanic$Age, na.rm = TRUE)
limit_superior <- q_edat[2] + 1.5 * iqr_edat

titanic_sense_outliers <- titanic %>% 
  filter(Age <= limit_superior | is.na(Age))

boxplot(titanic_sense_outliers$Age, 
        horizontal = TRUE, 
        col = "#81C784", 
        main = "Edat sense outliers extrems")

# Exercici 5: Fare per sexe amb boxplot
boxplot(Fare ~ sexe, data = titanic, 
        col = c("#90CAF9", "#F48FB1"),
        outline = FALSE, # Ometem outliers per millorar la visualització
        main = "Distribució de la tarifa per sexe",
        ylab = "Tarifa ($)")

# Exercici 6: Histograma d'alçada d'Starwars
hist(starwars$height, 
     col = "#FFE082", 
     main = "Alçada dels personatges d'Starwars",
     xlab = "Alçada (cm)")

# Exercici 7: Personatge més pesat
starwars %>% 
  select(name, mass) %>% 
  arrange(desc(mass)) %>% 
  head(n = 3)
# A tibble: 3 × 2
  name                   mass
  <chr>                 <dbl>
1 Jabba Desilijic Tiure  1358
2 Grievous                159
3 IG-88                   140
# Exercici 8: Pes d'Starwars per sexe
starwars %>% 
  filter(!is.na(mass), !is.na(sex)) %>% 
  group_by(sex) %>% 
  summarise(
    n = n(),
    mediana_pes = median(mass),
    p25 = quantile(mass, 0.25),
    p75 = quantile(mass, 0.75)
  )
# A tibble: 4 × 5
  sex                n mediana_pes    p25    p75
  <chr>          <int>       <dbl>  <dbl>  <dbl>
1 female             9        55     50     56.2
2 hermaphroditic     1      1358   1358   1358  
3 male              42        80     75.5   86.2
4 none               4        53.5   32     91.2
# Exercici 9: Inspecció d'iris
glimpse(iris)
Rows: 150
Columns: 5
$ Sepal.Length <dbl> 5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9, 5.4, 4.…
$ Sepal.Width  <dbl> 3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.…
$ Petal.Length <dbl> 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.…
$ Petal.Width  <dbl> 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.…
$ Species      <fct> setosa, setosa, setosa, setosa, setosa, setosa, setosa, s…
summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
 Median :5.800   Median :3.000   Median :4.350   Median :1.300  
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
       Species  
 setosa    :50  
 versicolor:50  
 virginica :50  
                
                
                
# Exercici 10: Gràfic ggplot2 d'iris per a publicació
ggplot(iris, aes(x = Species, y = Petal.Length, fill = Species)) +
  geom_boxplot(alpha = 0.7, outlier.shape = NA) +
  geom_jitter(width = 0.15, alpha = 0.6, size = 2) +
  theme_classic() +
  labs(
    title = "Longitud del pètal segons espècie",
    x = "Espècie",
    y = "Longitud del pètal (cm)"
  ) +
  theme(legend.position = "none")

# Exercici 11: Taula 1 automatitzada
titanic %>% 
  select(supervivent, Age, sexe, Pclass, Fare) %>% 
  tbl_summary(
    by = supervivent,
    statistic = list(
      Age ~ "{mean} ({sd})",
      Fare ~ "{median} ({p25}, {p75})"
    )
  ) %>% 
  add_overall() %>% 
  bold_labels()
Characteristic Overall
N = 2001
Mort
N = 1101
Viu
N = 901
Age 30 (14) 30 (13) 31 (15)
sexe


    Home 84 (42%) 35 (32%) 49 (54%)
    Dona 116 (58%) 75 (68%) 41 (46%)
Pclass


    1 47 (24%) 20 (18%) 27 (30%)
    2 64 (32%) 40 (36%) 24 (27%)
    3 89 (45%) 50 (45%) 39 (43%)
Fare 32 (15, 54) 32 (16, 52) 33 (14, 55)
1 Mean (SD); n (%); Median (Q1, Q3)