library(tidyverse)
library(readxl)
library(naniar)
library(janitor)Curs d’Estadística amb R per a Metges - Capítol 2: Estadística Descriptiva
Estadística descriptiva i exploració de dades
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.
- 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.
- Llegir dades des d’un format extern (
- 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()).
- Triar les columnes rellevants de la cohort (
- 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.
- 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 columna2. 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)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: NAPer 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: 153. 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
- 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).
- 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).
- Un nombre es guarda com a
numeric(doubleointeger). - 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èriquesRebatejar 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
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ó:
- 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).
- 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
- 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)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()| 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)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:
- Crea un script o document Quarto per resoldre els exercicis i organitza el teu codi amb comentaris clars.
- Instal·la (si cal) el paquet
medicaldatai carrega un dels seus conjunts de dades clíniques (per exemple,opt). - Fes una inspecció estructural completa de les dades del Titanic, incloent l’auditoria de valors absents amb
naniar. - Identifica i filtra els passatgers que representen valors atípics (outliers) a l’edat del Titanic i torna a representar el boxplot.
- Compara el preu del bitllet (
Fare) segons el sexe de forma gràfica utilitzant un diagrama de caixes. - Fes un histograma de l’alçada dels personatges del conjunt de dades
starwars(inclòs adplyr). - Selecciona les columnes de nom i massa (
mass) destarwarsi ordena-les per esbrinar quin personatge és el més pesat. - Compara numèricament (mitjana o mediana) i gràficament el pes dels personatges d’Starwars segons el seu gènere.
- Inspecciona el dataset
iris, obtén la llista de variables i el seu resum descriptiu. - Construeix una figura d’alta resolució amb
ggplot2a partir de les dades d’irismostrant la distribució de longituds de pètal per espècie. - Genera una Taula 1 automatitzada amb
compareGroupsogtsummaryestratificant 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) | |||