Relatório de Análise de Cluster

Author

Caio Sain Vallio e Vitor Sain Vallio

Published

March 8, 2025

Code
set.seed(123)

# Libraries
library(tidyverse)

# Read data
df <- read_csv2("data/CopyPERFILEPIDEMIOLG_DATA_LABELS_2025-03-06_2048.csv")

# rename variables
df <- df |>
  select(
    # demographic variables
    idade = "Idade",
    sexo = "Sexo",
    imc = "IMC",
    renda_familiar = "Renda Familiar",
    nivel_instrucao = "Nível de Instrução",
    estado_civil = "Estado Civil",
    # descriptive variables
    tipo_fratura = "Tipo de Fratura",
    historico_previo_trauma = "Histórico Prévio de Trauma",
    fisioterapia_pre_operatoria = "Fisioterapia Pré Operatória",
    infeccao_hospitalar = "Infecção Hospitalar",
    tempo_internacao = "Tempo de Internação",
    tempo_cirurgia = "Tempo de Cirurgia",
    fraturas_membro_superior = "Subdivisão Diagnósticos (choice=Fraturas de Membro Superior)",
    fraturas_membro_inferior = "Subdivisão Diagnósticos (choice=Fraturas de Membro Inferior)",
    fraturas_coluna_vertebral = "Subdivisão Diagnósticos (choice=Fraturas de Coluna Vertebral)",
    hads_ansiedade = "HADS Ansiedade",
    hads_depressao = "HADS Depressão",
    tampa_escore_final = "TAMPA Escore Final",
    pcs_escore_final = "Escore PCS Final",
    # cluster variables
    escore_mif = "Escore MIF",
    escala_visual_analogica = "Escala Visual Analógica",
    escore_final_eq5d = "Escore Final EQ5D",
    regua_eq5d = "Nós gostaríamos de saber o quão boa ou ruim a sua saúde está HOJE.    Esta escala é numerada de 0 a 100.    100 significa a melhor saúde que você possa imaginar.  0 significa a pior saúde que você possa imaginar.  Por favor clique na escala para indicar como a sua saúde está HOJE.",
    injury_severity_score = "Injury Severity Score",
  ) |>
  drop_na()


# boolean variables
df <- df |>
  mutate(
    fraturas_membro_superior = if_else(
      fraturas_membro_superior == "Checked",
      TRUE,
      FALSE
    ),
    fraturas_membro_inferior = if_else(
      fraturas_membro_inferior == "Checked",
      TRUE,
      FALSE
    ),
    fraturas_coluna_vertebral = if_else(
      fraturas_coluna_vertebral == "Checked",
      TRUE,
      FALSE
    )
  ) |>
  mutate(
    regiao_fratura = case_when(
      fraturas_membro_superior &
        !fraturas_membro_inferior &
        !fraturas_coluna_vertebral ~
        "Membro Superior",
      !fraturas_membro_superior &
        fraturas_membro_inferior &
        !fraturas_coluna_vertebral ~
        "Membro Inferior",
      !fraturas_membro_superior &
        !fraturas_membro_inferior &
        fraturas_coluna_vertebral ~
        "Coluna Vertebral",
      fraturas_membro_superior &
        fraturas_membro_inferior &
        !fraturas_coluna_vertebral ~
        "Membro Superior e Inferior",
      fraturas_membro_superior &
        !fraturas_membro_inferior &
        fraturas_coluna_vertebral ~
        "Membro Superior e Coluna Vertebral",
      !fraturas_membro_superior &
        fraturas_membro_inferior &
        fraturas_coluna_vertebral ~
        "Membro Inferior e Coluna Vertebral",
      fraturas_membro_superior &
        fraturas_membro_inferior &
        fraturas_coluna_vertebral ~
        "Membro Superior, Inferior e Coluna Vertebral",
      TRUE ~ NA_character_
    )
  )

# Factor variables
df <- df |>
  mutate(
    renda_familiar = factor(
      renda_familiar,
      levels = c(
        "< 1 salário mínimo",
        "1 salário mínimo",
        "1 a 2 salários mínimos",
        "2 a 3 salários mínimos",
        "3 a 4 salários mínimos",
        "> 4 salários mínimos"
      )
    )
  ) |>
  mutate(
    nivel_instrucao = factor(
      nivel_instrucao,
      levels = c(
        "Sem escolaridade",
        "Ensino Fundamental Incompleto",
        "Ensino Fundamental Completo",
        "Ensino Médio Incompleto",
        "Ensino Médio Completo",
        "Ensino Superior Incompleto",
        "Ensino Superior Completo"
      )
    )
  )

# Filter out missing values
df <- df |> filter(!is.na(regiao_fratura))

# Scale variables
df <- df |>
  mutate(
    scaled_escore_mif = scale(escore_mif),
    scaled_escala_visual_analogica = scale(escala_visual_analogica),
    scaled_escore_final_eq5d = scale(escore_final_eq5d),
    scaled_regua_eq5d = scale(regua_eq5d),
    scaled_injury_severity_score = scale(injury_severity_score)
  )

1 Exploratory data analysis

1.1 Cluster variables

Code
df |>
  gtsummary::tbl_summary(
    include = c(
      escore_mif,
      escala_visual_analogica,
      escore_final_eq5d,
      regua_eq5d,
      injury_severity_score
    ),
    statistic = list(
      gtsummary::all_continuous() ~ "{mean} ({sd})",
      gtsummary::all_categorical() ~ "{n} / {N} ({p}%)"
    ),
    digits = gtsummary::all_continuous() ~ 2,
    label = list(
      escore_mif = "Escore MIF",
      escala_visual_analogica = "Escala Visual Analógica",
      escore_final_eq5d = "Escore Final EQ5D",
      regua_eq5d = "Régua EQ5D",
      injury_severity_score = "Injury Severity Score"
    )
  )
Characteristic N = 6081
Escore MIF 101.49 (21.84)
Escala Visual Analógica 4.11 (3.15)
Escore Final EQ5D 0.52 (0.26)
Régua EQ5D 70.99 (22.81)
Injury Severity Score 6.37 (3.88)
1 Mean (SD)

1.2 Demographic variables

Code
df |>
  gtsummary::tbl_summary(
    include = c(
      idade,
      sexo,
      imc,
      renda_familiar,
      nivel_instrucao,
      estado_civil
    ),
    statistic = list(
      gtsummary::all_continuous() ~ "{mean} ({sd})",
      gtsummary::all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      idade = "Idade",
      sexo = "Sexo",
      imc = "IMC",
      renda_familiar = "Renda Familiar",
      nivel_instrucao = "Nível de Instrução",
      estado_civil = "Estado Civil"
    )
  )
Characteristic N = 6081
Idade 45 (17)
Sexo
    Feminino 183 (30%)
    Masculino 425 (70%)
IMC 26.0 (5.3)
Renda Familiar
    < 1 salário mínimo 58 (9.5%)
    1 salário mínimo 95 (16%)
    1 a 2 salários mínimos 172 (28%)
    2 a 3 salários mínimos 128 (21%)
    3 a 4 salários mínimos 74 (12%)
    > 4 salários mínimos 81 (13%)
Nível de Instrução
    Sem escolaridade 18 (3.0%)
    Ensino Fundamental Incompleto 118 (19%)
    Ensino Fundamental Completo 74 (12%)
    Ensino Médio Incompleto 94 (15%)
    Ensino Médio Completo 186 (31%)
    Ensino Superior Incompleto 40 (6.6%)
    Ensino Superior Completo 78 (13%)
Estado Civil
    Casado 152 (25%)
    Solteiro 362 (60%)
    União Estável 55 (9.0%)
    Viúvo 39 (6.4%)
1 Mean (SD); n (%)

1.3 Descriptive variables

Code
df |>
  gtsummary::tbl_summary(
    include = c(
      tipo_fratura,
      historico_previo_trauma,
      fisioterapia_pre_operatoria,
      infeccao_hospitalar,
      tempo_internacao,
      tempo_cirurgia,
      regiao_fratura,
      hads_ansiedade,
      hads_depressao,
      tampa_escore_final,
      pcs_escore_final
    ),
    statistic = list(
      gtsummary::all_continuous() ~ "{mean} ({sd})",
      gtsummary::all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      tipo_fratura = "Tipo de Fratura",
      historico_previo_trauma = "Histórico Prévio de Trauma",
      fisioterapia_pre_operatoria = "Fisioterapia Pré Operatória",
      infeccao_hospitalar = "Infecção Hospitalar",
      tempo_internacao = "Tempo de Internação",
      tempo_cirurgia = "Tempo de Cirurgia",
      regiao_fratura = "Região da Fratura",
      hads_ansiedade = "HADS Ansiedade",
      hads_depressao = "HADS Depressão",
      tampa_escore_final = "TAMPA Escore Final",
      pcs_escore_final = "Escore PCS Final"
    )
  )
Characteristic N = 6081
Tipo de Fratura
    Aberta 110 (18%)
    Ambos 34 (5.6%)
    Fechada 464 (76%)
Histórico Prévio de Trauma 245 (40%)
Fisioterapia Pré Operatória 292 (48%)
Infecção Hospitalar 21 (3.5%)
Tempo de Internação 7.7 (9.3)
Tempo de Cirurgia 199 (101)
Região da Fratura
    Coluna Vertebral 13 (2.1%)
    Membro Inferior 351 (58%)
    Membro Inferior e Coluna Vertebral 7 (1.2%)
    Membro Superior 189 (31%)
    Membro Superior e Coluna Vertebral 2 (0.3%)
    Membro Superior e Inferior 42 (6.9%)
    Membro Superior, Inferior e Coluna Vertebral 4 (0.7%)
HADS Ansiedade 11.4 (3.0)
HADS Depressão 8.09 (2.45)
TAMPA Escore Final 41 (8)
Escore PCS Final 19 (13)
1 n (%); Mean (SD)

2 Correlation matrix

Code
# correlations
df |>
  select(
    scaled_escore_mif,
    scaled_escala_visual_analogica,
    scaled_escore_final_eq5d,
    scaled_regua_eq5d,
    scaled_injury_severity_score
  ) |>
  rename(
    "MIF" = scaled_escore_mif,
    "EVA" = scaled_escala_visual_analogica,
    "EQ5D" = scaled_escore_final_eq5d,
    "REQ5D" = scaled_regua_eq5d,
    "ISS" = scaled_injury_severity_score
  ) |>
  write.csv2("data/cluster_variables.csv", row.names = FALSE)


read_csv2("data/cluster_variables.csv") |>
  GGally::ggpairs() +
  theme_classic()

3 Hopkins test

A estatística de Hopkins é usada para avaliar a tendência de agrupamento de um conjunto de dados medindo a probabilidade de que um dado conjunto de dados seja gerado por uma distribuição uniforme de dados. Em outras palavras, ela testa a aleatoriedade espacial dos dados.

Valores calculados de 0 a 0,3 indicam dados regularmente espaçados. Valores em torno de 0,5 indicam dados aleatórios. Valores de 0,7 a 1 indicam dados que podem ser agrupados. Hopkins and Skellam (1954)

Code
hopkins::hopkins(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    ),
  k = 2
)
[1] 0.9622877

4 Visual assessment of cluster tendency (VAT)

O VAT detecta a tendência de agrupamento de forma visual contando o número de blocos escuros quadrados ao longo da diagonal em uma imagem do VAT. Bezdek and Hathaway (2002)

Code
factoextra::fviz_dist(
  dist(
    df |>
      select(
        scaled_escore_mif,
        scaled_escala_visual_analogica,
        scaled_escore_final_eq5d,
        scaled_regua_eq5d,
        scaled_injury_severity_score
      )
  )
)

5 Cluster analysis

5.1 Determinating the number of clusters

5.1.1 Elbow Method

Este método envolve plotar a soma dos quadrados dentro do cluster (WSS) como uma função do número de clusters. O WSS é uma medida de quão bem os pontos de dados estão agrupados. O método do cotovelo funciona procurando o ponto na curva WSS onde a taxa de diminuição começa a desacelerar. Este ponto é frequentemente considerado o número ótimo de clusters.

Code
# factoextra::fviz_nbclust(
#     df |>
#         select(
#             scaled_escore_mif,
#             scaled_escala_visual_analogica,
#             scaled_escore_final_eq5d,
#             scaled_regua_eq5d,
#             scaled_injury_severity_score
#         ),
#     kmeans,
#     method = "wss"
# ) +
#     geom_vline(xintercept = 2, linetype = 2) +
#     theme_classic() +
#     labs(
#         title = "Elbow Method",
#         x = "Number of Clusters (k)",
#         y = "Total Within Sum of Square"
#     ) +
#     theme(
#         plot.title = element_text(size = 16, face = "bold"),
#         axis.title = element_text(size = 14),
#         axis.text = element_text(size = 12)
#     )

elbow <- parameters::n_clusters_elbow(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    )
)
print(elbow)
The Elbow method, that aims at minimizing the total intra-cluster variation (i.e., the total within-cluster sum of square), suggests that the optimal number of clusters is 2.
Code
plot(elbow) +
  theme_classic()

5.1.2 Silhouette Method

A pontuação da silhueta é uma medida de quão bem cada ponto de dados está agrupado com seu próprio cluster em comparação a outros clusters. Ela varia de -1 a 1, com um valor de 1 indicando que o ponto de dados está perfeitamente agrupado com seu próprio cluster, 0 indicando que o ponto de dados está na borda entre os dois clusters e um valor de -1 indicando que o ponto de dados está igualmente bem agrupado com dois ou mais clusters. O número ideal de clusters é frequentemente o número que maximiza a pontuação média da silhueta. Rousseeuw (1987)

Code
# factoextra::fviz_nbclust(
#     df |>
#         select(
#             scaled_escore_mif,
#             scaled_escala_visual_analogica,
#             scaled_escore_final_eq5d,
#             scaled_regua_eq5d,
#             scaled_injury_severity_score
#         ),
#     kmeans,
#     method = "silhouette"
# ) +
#     theme_classic() +
#     labs(
#         title = "Silhouette Method",
#         x = "Number of Clusters (k)",
#         y = "Silhouette Width"
#     )

# k <- 2
# kmeans_clustering <- kmeans(
#     df |>
#         select(
#             scaled_escore_mif,
#             scaled_escala_visual_analogica,
#             scaled_escore_final_eq5d,
#             scaled_regua_eq5d,
#             scaled_injury_severity_score
#         ),
#     centers = k,
#     nstart = 20
# )

# sil <- cluster::silhouette(
#     kmeans_clustering$cluster,
#     dist(
#         df |>
#             select(
#                 scaled_escore_mif,
#                 scaled_escala_visual_analogica,
#                 scaled_escore_final_eq5d,
#                 scaled_regua_eq5d,
#                 scaled_injury_severity_score
#             )
#     )
# )
# factoextra::fviz_silhouette(sil) +
#     theme_classic()
# > Um coeficiente de silhueta positivo indica que uma observação está bem adaptada ao seu próprio cluster.

silhouette <- parameters::n_clusters_silhouette(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    )
)
print(silhouette)
The Silhouette method, based on the average quality of clustering, suggests that the optimal number of clusters is 2.
Code
plot(silhouette) +
  theme_classic()

5.1.3 Gap statistic method

A estatística de gap ajuda a determinar o número ótimo de clusters comparando a dispersão observada dentro do cluster com uma distribuição de referência. A ideia é selecionar o número de clusters que maximiza a estatística de gap. Um gap maior indica um desvio mais significativo da aleatoriedade, sugerindo uma estrutura de clustering mais bem definida. Ao comparar os gaps para diferentes números de clusters, podemos identificar o número de clusters que fornece o padrão de clustering mais distinto e significativo. Tibshirani and Walther (2005)

Code
# factoextra::fviz_nbclust(
#     df |>
#         select(
#             scaled_escore_mif,
#             scaled_escala_visual_analogica,
#             scaled_escore_final_eq5d,
#             scaled_regua_eq5d,
#             scaled_injury_severity_score
#         ),
#     kmeans,
#     method = "gap_stat"
# ) +
#     theme_classic() +
#     labs(
#         title = "Gap Statistic Method",
#         x = "Number of Clusters (k)",
#         y = "Gap Statistic"
#     )

gap <- parameters::n_clusters_gap(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    )
)
print(gap)
The Gap method, that compares the total intracluster variation of k clusters with their expected values under null reference distribution of the data, suggests that the optimal number of clusters is 2.
Code
plot(gap) +
  theme_classic()

5.1.4 Volting methods

A maneira mais inteligente é experimentar métodos diferentes e ver qual a maioria retorna. Charrad et al. (2014)

Code
n_clust <- parameters::n_clusters(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    ),
  package = c("easystats", "NbClust", "mclust", "M3C"),
  standardize = TRUE
)
print(n_clust)
# Method Agreement Procedure:

The choice of 2 clusters is supported by 14 (48.28%) methods out of 29 (Elbow, Silhouette, Gap_Maechler2012, Gap_Dudoit2002, Ch, CCC, Duda, Pseudot2, Beale, Ratkowsky, PtBiserial, Frey, Mcclain, Dunn).
Code
plot(n_clust) +
  theme_classic()

6 Appling cluster methods

6.1 K-means clustering

Code
kmeans_clustering <- parameters::cluster_analysis(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    ),
  method = "kmeans",
  n = 2
)

kmeans_clustering
# Clustering Solution

The 2 clusters accounted for 30.20% of the total variance of the original data.

Cluster | n_Obs | Sum_Squares | scaled_escore_mif | scaled_escala_visual_analogica | scaled_escore_final_eq5d | scaled_regua_eq5d | scaled_injury_severity_score
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1       |   242 |     1139.86 |             -0.86 |                           0.51 |                    -0.89 |             -0.67 |                         0.21
2       |   366 |      978.46 |              0.57 |                          -0.34 |                     0.59 |              0.44 |                        -0.14

  Sum_Squares_Total Sum_Squares_Between Sum_Squares_Within        R2
1              3035            916.6808           2118.319 0.3020365

# You can access the predicted clusters via `predict()`.
Code
parameters::cluster_discrimination(
  kmeans_clustering,
)
# Accuracy of Cluster Group Classification via Linear Discriminant Analysis (LDA)

 Group Accuracy
     1  100.00%
     2   94.63%

Overall accuracy of classification: 97.86%
Code
kmeans_clustering |> plot() + theme_classic()

Code
kmeans_clustering |>
  summary() |>
  plot() +
  theme_classic()

6.2 Hierarchical K-means clustering

Code
hkmeans_clustering <- parameters::cluster_analysis(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    ),
  method = "hkmeans",
  n = 2
)

hkmeans_clustering
# Clustering Solution

The 2 clusters accounted for 30.20% of the total variance of the original data.

Cluster | n_Obs | Sum_Squares | scaled_escore_mif | scaled_escala_visual_analogica | scaled_escore_final_eq5d | scaled_regua_eq5d | scaled_injury_severity_score
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1       |   366 |      978.46 |              0.57 |                          -0.34 |                     0.59 |              0.44 |                        -0.14
2       |   242 |     1139.86 |             -0.86 |                           0.51 |                    -0.89 |             -0.67 |                         0.21

  Sum_Squares_Total Sum_Squares_Between Sum_Squares_Within        R2
1              3035            916.6808           2118.319 0.3020365

# You can access the predicted clusters via `predict()`.
Code
parameters::cluster_discrimination(
  hkmeans_clustering,
)
# Accuracy of Cluster Group Classification via Linear Discriminant Analysis (LDA)

 Group Accuracy
     1  100.00%
     2   94.63%

Overall accuracy of classification: 97.86%
Code
hkmeans_clustering |> plot() + theme_classic()

Code
hkmeans_clustering |>
  summary() |>
  plot() +
  theme_classic()

6.3 K-medoids clustering

Code
kmedoids_clustering <- parameters::cluster_analysis(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    ),
  method = "pam",
  n = 2
)

kmedoids_clustering
# Clustering Solution

The 2 clusters accounted for 29.86% of the total variance of the original data.

Cluster | n_Obs | Sum_Squares | scaled_escore_mif | scaled_escala_visual_analogica | scaled_escore_final_eq5d | scaled_regua_eq5d | scaled_injury_severity_score
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1       |   357 |      995.02 |              0.58 |                          -0.39 |                     0.59 |              0.44 |                        -0.11
2       |   251 |     1133.68 |             -0.83 |                           0.56 |                    -0.85 |             -0.62 |                         0.16

  Sum_Squares_Total Sum_Squares_Between Sum_Squares_Within        R2
1              3035            906.3024           2128.698 0.2986169

# You can access the predicted clusters via `predict()`.
Code
parameters::cluster_discrimination(
  kmedoids_clustering,
)
# Accuracy of Cluster Group Classification via Linear Discriminant Analysis (LDA)

 Group Accuracy
     1   99.72%
     2   91.24%

Overall accuracy of classification: 96.22%
Code
kmedoids_clustering |> plot() + theme_classic()

Code
kmedoids_clustering |>
  summary() |>
  plot() +
  theme_classic()

6.4 Hierarchical clustering

Code
hclust_clustering <- parameters::cluster_analysis(
  df |>
    select(
      scaled_escore_mif,
      scaled_escala_visual_analogica,
      scaled_escore_final_eq5d,
      scaled_regua_eq5d,
      scaled_injury_severity_score
    ),
  method = "hclust",
  n = 2
)

hclust_clustering
# Clustering Solution

The 2 clusters accounted for 12.73% of the total variance of the original data.

Cluster | n_Obs | Sum_Squares | scaled_escore_mif | scaled_escala_visual_analogica | scaled_escore_final_eq5d | scaled_regua_eq5d | scaled_injury_severity_score
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1       |   483 |     2094.10 |              0.04 |                          -0.09 |                     0.09 |              0.06 |                        -0.38
2       |   125 |      554.60 |             -0.15 |                           0.35 |                    -0.33 |             -0.21 |                         1.47

  Sum_Squares_Total Sum_Squares_Between Sum_Squares_Within        R2
1              3035            386.2993           2648.701 0.1272815

# You can access the predicted clusters via `predict()`.
Code
parameters::cluster_discrimination(
  hclust_clustering,
)
# Accuracy of Cluster Group Classification via Linear Discriminant Analysis (LDA)

 Group Accuracy
     1   97.10%
     2   75.20%

Overall accuracy of classification: 92.60%
Code
hclust_clustering |> plot() + theme_classic()

Code
hclust_clustering |>
  summary() |>
  plot() +
  theme_classic()

7 Results

A partir dos dados, K-means e K-means hierárquicos têm desempenho semelhante (R² = 0,302, precisão = 97,86%), enquanto K-medoids é ligeiramente inferior (R² = 0,299, precisão = 96,22%). O agrupamento hierárquico tem o pior desempenho (R² = 0,127, precisão = 92,60%).

7.1 K-means clustering

Code
df$kmeans_cluster <- kmeans_clustering |> predict()

7.1.1 Cluster variables

Code
df |>
  gtsummary::tbl_summary(
    include = c(
      escore_mif,
      escala_visual_analogica,
      escore_final_eq5d,
      regua_eq5d,
      injury_severity_score
    ),
    by = kmeans_cluster,
    statistic = list(
      gtsummary::all_continuous() ~ "{mean} ({sd})",
      gtsummary::all_categorical() ~ "{n} / {N} ({p}%)"
    ),
    digits = gtsummary::all_continuous() ~ 2,
    label = list(
      escore_mif = "Escore MIF",
      escala_visual_analogica = "Escala Visual Analógica",
      escore_final_eq5d = "Escore Final EQ5D",
      regua_eq5d = "Régua EQ5D",
      injury_severity_score = "Injury Severity Score"
    )
  ) |>
  gtsummary::add_p() |>
  gtsummary::bold_p() |>
  gtsummary::add_ci() # |> gtsummary::as_hux_xlsx(file = "cluster_variables.xlsx")
Characteristic 1
N = 242
1
95% CI 2
N = 366
1
95% CI p-value2
Escore MIF 82.73 (18.91) 80, 85 113.89 (13.02) 113, 115 <0.001
Escala Visual Analógica 5.71 (3.12) 5.3, 6.1 3.04 (2.69) 2.8, 3.3 <0.001
Escore Final EQ5D 0.30 (0.20) 0.27, 0.32 0.67 (0.16) 0.66, 0.69 <0.001
Régua EQ5D 55.72 (23.61) 53, 59 81.09 (15.50) 79, 83 <0.001
Injury Severity Score 7.19 (4.43) 6.6, 7.8 5.82 (3.37) 5.5, 6.2 <0.001
Abbreviation: CI = Confidence Interval
1 Mean (SD)
2 Wilcoxon rank sum test

7.1.2 Demographic variables

Code
df |>
  gtsummary::tbl_summary(
    include = c(
      idade,
      sexo,
      imc,
      renda_familiar,
      nivel_instrucao,
      estado_civil
    ),
    by = kmeans_cluster,
    statistic = list(
      gtsummary::all_continuous() ~ "{mean} ({sd})",
      gtsummary::all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      idade = "Idade",
      sexo = "Sexo",
      imc = "IMC",
      renda_familiar = "Renda Familiar",
      nivel_instrucao = "Nível de Instrução",
      estado_civil = "Estado Civil"
    )
  ) |>
  gtsummary::add_p() |>
  gtsummary::bold_p() |>
  gtsummary::add_ci() |>
  gtsummary::separate_p_footnotes() # |> gtsummary::as_hux_xlsx(file = "demografic_variables.xlsx")
Characteristic 1
N = 242
1
95% CI 2
N = 366
1
95% CI p-value
Idade 50 (19) 47, 52 42 (15) 40, 44 <0.0012
Sexo



<0.0013
    Feminino 94 (39%) 33%, 45% 89 (24%) 20%, 29%
    Masculino 148 (61%) 55%, 67% 277 (76%) 71%, 80%
IMC 26.2 (5.6) 25, 27 25.8 (5.1) 25, 26 0.52
Renda Familiar



<0.0013
    < 1 salário mínimo 31 (13%) 9.0%, 18% 27 (7.4%) 5.0%, 11%
    1 salário mínimo 52 (21%) 17%, 27% 43 (12%) 8.7%, 16%
    1 a 2 salários mínimos 72 (30%) 24%, 36% 100 (27%) 23%, 32%
    2 a 3 salários mínimos 40 (17%) 12%, 22% 88 (24%) 20%, 29%
    3 a 4 salários mínimos 22 (9.1%) 5.9%, 14% 52 (14%) 11%, 18%
    > 4 salários mínimos 25 (10%) 6.9%, 15% 56 (15%) 12%, 19%
Nível de Instrução



0.0203
    Sem escolaridade 14 (5.8%) 3.3%, 9.7% 4 (1.1%) 0.35%, 3.0%
    Ensino Fundamental Incompleto 54 (22%) 17%, 28% 64 (17%) 14%, 22%
    Ensino Fundamental Completo 26 (11%) 7.3%, 16% 48 (13%) 9.9%, 17%
    Ensino Médio Incompleto 36 (15%) 11%, 20% 58 (16%) 12%, 20%
    Ensino Médio Completo 67 (28%) 22%, 34% 119 (33%) 28%, 38%
    Ensino Superior Incompleto 17 (7.0%) 4.3%, 11% 23 (6.3%) 4.1%, 9.4%
    Ensino Superior Completo 28 (12%) 8.0%, 16% 50 (14%) 10%, 18%
Estado Civil



0.0493
    Casado 53 (22%) 17%, 28% 99 (27%) 23%, 32%
    Solteiro 146 (60%) 54%, 66% 216 (59%) 54%, 64%
    União Estável 20 (8.3%) 5.2%, 13% 35 (9.6%) 6.8%, 13%
    Viúvo 23 (9.5%) 6.2%, 14% 16 (4.4%) 2.6%, 7.1%
Abbreviation: CI = Confidence Interval
1 Mean (SD); n (%)
2 Wilcoxon rank sum test
3 Pearson’s Chi-squared test

7.1.3 Descriptive variables

Code
chisq.test.simulate <- function(data, variable, by, ...) {
  test_results <- stats::chisq.test(
    data[[variable]],
    data[[by]],
    simulate.p.value = TRUE,
    B = 10000
  )
  
  # Return results as a data frame
  data.frame(
    estimate = NA_real_,
    p.value = test_results$p.value,
    conf.low = NA_real_,
    conf.high = NA_real_,
    method = test_results$method,
    stringsAsFactors = FALSE
  )
}

df |>
  gtsummary::tbl_summary(
    include = c(
      tipo_fratura,
      historico_previo_trauma,
      fisioterapia_pre_operatoria,
      infeccao_hospitalar,
      tempo_internacao,
      tempo_cirurgia,
      regiao_fratura,
      hads_ansiedade,
      hads_depressao,
      tampa_escore_final,
      pcs_escore_final
    ),
    by = kmeans_cluster,
    statistic = list(
      gtsummary::all_continuous() ~ "{mean} ({sd})",
      gtsummary::all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      tipo_fratura = "Tipo de Fratura",
      historico_previo_trauma = "Histórico Prévio de Trauma",
      fisioterapia_pre_operatoria = "Fisioterapia Pré Operatória",
      infeccao_hospitalar = "Infecção Hospitalar",
      tempo_internacao = "Tempo de Internação",
      tempo_cirurgia = "Tempo de Cirurgia",
      regiao_fratura = "Região da Fratura",
      hads_ansiedade = "HADS Ansiedade",
      hads_depressao = "HADS Depressão",
      tampa_escore_final = "TAMPA Escore Final",
      pcs_escore_final = "Escore PCS Final"
    )
  ) |>
  gtsummary::add_p(
    test = list(
      regiao_fratura ~ "chisq.test.simulate"
    )
  ) |>
  gtsummary::bold_p() |>
  gtsummary::add_ci() |>
  gtsummary::separate_p_footnotes() # |> gtsummary::as_hux_xlsx(file = "descriptive_variables.xlsx")
Characteristic 1
N = 242
1
95% CI 2
N = 366
1
95% CI p-value
Tipo de Fratura



<0.0012
    Aberta 32 (13%) 9.3%, 18% 78 (21%) 17%, 26%
    Ambos 23 (9.5%) 6.2%, 14% 11 (3.0%) 1.6%, 5.5%
    Fechada 187 (77%) 71%, 82% 277 (76%) 71%, 80%
Histórico Prévio de Trauma 98 (40%) 34%, 47% 147 (40%) 35%, 45% >0.92
Fisioterapia Pré Operatória 135 (56%) 49%, 62% 157 (43%) 38%, 48% 0.0022
Infecção Hospitalar 13 (5.4%) 3.0%, 9.2% 8 (2.2%) 1.0%, 4.4% 0.0352
Tempo de Internação 8.6 (10.9) 7.3, 10 7.1 (8.0) 6.3, 7.9 <0.0013
Tempo de Cirurgia 223 (101) 210, 236 183 (98) 173, 194 <0.0013
Região da Fratura



<0.0014
    Coluna Vertebral 10 (4.1%) 2.1%, 7.7% 3 (0.8%) 0.21%, 2.6%
    Membro Inferior 160 (66%) 60%, 72% 191 (52%) 47%, 57%
    Membro Inferior e Coluna Vertebral 3 (1.2%) 0.32%, 3.9% 4 (1.1%) 0.35%, 3.0%
    Membro Superior 39 (16%) 12%, 21% 150 (41%) 36%, 46%
    Membro Superior e Coluna Vertebral 1 (0.4%) 0.02%, 2.6% 1 (0.3%) 0.01%, 1.8%
    Membro Superior e Inferior 26 (11%) 7.3%, 16% 16 (4.4%) 2.6%, 7.1%
    Membro Superior, Inferior e Coluna Vertebral 3 (1.2%) 0.32%, 3.9% 1 (0.3%) 0.01%, 1.8%
HADS Ansiedade 10.8 (3.3) 10, 11 11.8 (2.7) 12, 12 <0.0013
HADS Depressão 7.80 (2.72) 7.5, 8.1 8.29 (2.24) 8.1, 8.5 0.0013
TAMPA Escore Final 44 (8) 43, 45 38 (7) 38, 39 <0.0013
Escore PCS Final 26 (13) 24, 28 14 (11) 13, 15 <0.0013
Abbreviation: CI = Confidence Interval
1 n (%); Mean (SD)
2 Pearson’s Chi-squared test
3 Wilcoxon rank sum test
4 Pearson’s Chi-squared test with simulated p-value (based on 10000 replicates)

References

Bezdek, James C, and Richard J Hathaway. 2002. “VAT: A Tool for Visual Assessment of (Cluster) Tendency.” In Proceedings of the 2002 International Joint Conference on Neural Networks. IJCNN’02 (Cat. No. 02CH37290), 3:2225–30. IEEE.
Charrad, Malika, Nadia Ghazzali, Véronique Boiteau, and Azam Niknafs. 2014. “NbClust: An r Package for Determining the Relevant Number of Clusters in a Data Set.” Journal of Statistical Software 61: 1–36.
Hopkins, Brian, and John Gordon Skellam. 1954. “A New Method for Determining the Type of Distribution of Plant Individuals.” Annals of Botany 18 (2): 213–27.
Rousseeuw, Peter J. 1987. “Silhouettes: A Graphical Aid to the Interpretation and Validation of Cluster Analysis.” Journal of Computational and Applied Mathematics 20: 53–65.
Tibshirani, Robert, and Guenther Walther. 2005. “Cluster Validation by Prediction Strength.” Journal of Computational and Graphical Statistics 14 (3): 511–28.