Trabalho de Análise de Dados Reais

Author

Vinicius Aquino

No texto a seguir, foi aplicada técnicas de análise de sobrevivência de dados de sobrevida de pacientes acometidos pela doença de chagas do Hospital das Clínicas da Faculdade de Medicina da Universidade Federal de Minas Gerais.

head(dados)
# A tibble: 6 × 12
  Tempo Obito Sexo   Idade Classe_Funcional IAM   Rochagan Beta_Bloqueador   Bnp
  <dbl> <dbl> <fct>  <dbl> <fct>            <fct> <fct>    <fct>           <dbl>
1  6.38     0 Femin…    59 3                Nao   Nao      Sim                NA
2  6.38     0 Femin…    60 1                Nao   Nao      Nao                NA
3  5.46     0 Femin…    55 3                Nao   Nao      Nao                NA
4  6.25     0 Femin…    72 1                Nao   Nao      Nao                NA
5  8.44     0 Femin…    48 3                Nao   Sim      Nao               544
6  7.77     0 Mascu…    88 3                Sim   Nao      Nao                NA
# ℹ 3 more variables: Anticorpos <dbl>, ECG_Result <fct>, FE <dbl>

Tratamento de Dados

Na base de dados há dados faltantes. Dados faltantes serão sempre um problema, não há métodos perfeitos para a substituição de dados faltantes. Como pode ser visto abaixo, colunas como Idade, Bnp e FE ficam fortemente afetadas pela falta de dados:

N = nrow(dados)
cat(N, "Linhas no Dataset \n")
1000 Linhas no Dataset 
for (column in colnames(dados)) {
  cat(column, "Tem ", N-nrow(na.omit(dados[column])), "Linhas Nulas \n")
  }
Tempo Tem  7 Linhas Nulas 
Obito Tem  0 Linhas Nulas 
Sexo Tem  0 Linhas Nulas 
Idade Tem  205 Linhas Nulas 
Classe_Funcional Tem  8 Linhas Nulas 
IAM Tem  1 Linhas Nulas 
Rochagan Tem  0 Linhas Nulas 
Beta_Bloqueador Tem  13 Linhas Nulas 
Bnp Tem  599 Linhas Nulas 
Anticorpos Tem  16 Linhas Nulas 
ECG_Result Tem  27 Linhas Nulas 
FE Tem  302 Linhas Nulas 

Neste caso, opta-se por não seguir com FE e Bnp, devido ao seu alto valor de dados faltantes. Idade permance devida a sua importância apriorística para a análise. Além disso, opta-se por excluir as demais linhas, assumindo que esses dedos faltantes tem um padrão aleatório e sua remoção não gerará dados enviesados.

dados <- na.omit(subset(dados, select=-c(Bnp, FE)))
cat("Novo Dataset: ", nrow(dados), " Antes Eram:", N)
Novo Dataset:  744  Antes Eram: 1000

Analisando valor das variáveis:

par(mfrow = c(3,4), tck = 0)



for (column in colnames(dados)) {

    if (column == "Tempo"){
    cat(sum(dados$Tempo==0), "Linhas com valor 0 na Coluna Tempo (Modelo Exponencial não permite) \n")
    hist(dados$Tempo)
  }
  
  else {
    if (column == "Anticorpos"){
    hist(dados$Anticorpos)
    }
    else
    {
      if (column == "Idade"){
    hist(dados$Idade)
    }
      else {barplot(table(dados[column]),
             main=column)}}
  }
}
0 Linhas com valor 0 na Coluna Tempo (Modelo Exponencial não permite) 

Os valores estão de acordo ao que foi documentado no dicionário de dados, então pode-se seguir com a analise.

Análise exploratória

Estimando um Kaplan-Meier sem o uso de covariáveis e plotando o modelo:

ekm <- survfit(Surv(Tempo, Obito) ~ 1, data = dados)
ggsurvplot(ekm)

Analisando covariáveis qualitativas:

Plotando um Kaplan-Meier condicionado as variáveis qualitativas

(quali = colnames(subset(dados, select=-c(Tempo, Obito, Anticorpos, Idade))))
[1] "Sexo"             "Classe_Funcional" "IAM"              "Rochagan"        
[5] "Beta_Bloqueador"  "ECG_Result"      
ekm_1 <- survfit(Surv(Tempo, Obito) ~ Sexo, data = dados)
ekm_2 <- survfit(Surv(Tempo, Obito) ~ Classe_Funcional, data = dados)
ekm_3 <- survfit(Surv(Tempo, Obito) ~ IAM, data = dados)
ekm_4 <- survfit(Surv(Tempo, Obito) ~ Rochagan, data = dados)
ekm_5 <- survfit(Surv(Tempo, Obito) ~ Beta_Bloqueador, data = dados)
ekm_6 <- survfit(Surv(Tempo, Obito) ~ ECG_Result, data = dados)


splots <- list()
splots[[1]] <- ggsurvplot(ekm_1, title = "Sexo", conf.int = TRUE)
splots[[2]] <- ggsurvplot(ekm_2, title = "Classe_Funcional", conf.int = TRUE)
splots[[3]] <- ggsurvplot(ekm_3, title = "IAM", conf.int = TRUE)
splots[[4]] <- ggsurvplot(ekm_4, title = "Rochagan", conf.int = TRUE)
splots[[5]] <- ggsurvplot(ekm_5, title = "Beta Bloqueador", conf.int = TRUE)
splots[[6]] <- ggsurvplot(ekm_6, title = "ECG_Result", conf.int = TRUE)

arrange_ggsurvplots(splots, ncol = 3, nrow =2, )

Todas as variáveis qualitativas pareceram promissoras para a análise. A Classe_Funcional parece ter uma diferençar entre o nível 1 e os demais, mas uma semalhança entre 2, 3 e 4.

Estudo do efeito das covariáveis quantitativas:

Analisando Anticorpos e Idade (quebrando as variáveis em 3 níveis):

ekm_7 <- survfit(Surv(Tempo, Obito) ~ cut_interval(Anticorpos, 3), data = dados)
ekm_8 <- survfit(Surv(Tempo, Obito) ~ cut_interval(Idade, 3), data = dados)

splots <- list()
splots[[1]] <- ggsurvplot(ekm_7, title = "Anticorpos", conf.int = TRUE)
splots[[2]] <- ggsurvplot(ekm_8, title = "Idade", conf.int = TRUE)

arrange_ggsurvplots(splots, ncol = 2, nrow =1)

Anticorpos não pareceram tão promissores quanto Idade como variáveis significativas.

Modelos de regressão paramétricos

m0ex <- survreg(Surv(Tempo, Obito) ~ ., dist = "exponential", data = dados)
m0we <- survreg(Surv(Tempo, Obito) ~ ., dist = "weibull", data = dados)
m0ln <- survreg(Surv(Tempo, Obito) ~ ., dist = "lognormal", data = dados)


summary(m0ex)

Call:
survreg(formula = Surv(Tempo, Obito) ~ ., data = dados, dist = "exponential")
                       Value Std. Error     z       p
(Intercept)         6.41e+00   7.99e-01  8.02 1.1e-15
SexoFeminino        7.56e-01   2.00e-01  3.79 0.00015
Idade              -2.64e-02   8.08e-03 -3.26 0.00110
Classe_Funcional2  -6.93e-01   3.06e-01 -2.26 0.02353
Classe_Funcional3  -2.76e-01   2.15e-01 -1.28 0.19888
Classe_Funcional4   1.53e+01   3.36e+03  0.00 0.99636
IAMSim             -5.28e-01   3.19e-01 -1.66 0.09781
RochaganSim         3.35e-01   2.87e-01  1.17 0.24303
Beta_BloqueadorSim -6.31e-01   2.03e-01 -3.11 0.00190
Anticorpos         -8.40e-03   4.38e-02 -0.19 0.84801
ECG_ResultMaiores  -1.28e+00   2.83e-01 -4.51 6.4e-06

Scale fixed at 1 

Exponential distribution
Loglik(model)= -487.9   Loglik(intercept only)= -537.9
    Chisq= 100.02 on 10 degrees of freedom, p= 5.4e-17 
Number of Newton-Raphson Iterations: 16 
n= 744 
summary(m0we)

Call:
survreg(formula = Surv(Tempo, Obito) ~ ., data = dados, dist = "weibull")
                       Value Std. Error     z       p
(Intercept)         3.95e+00   3.93e-01 10.06 < 2e-16
SexoFeminino        3.54e-01   9.31e-02  3.80 0.00014
Idade              -1.17e-02   3.71e-03 -3.15 0.00166
Classe_Funcional2  -3.37e-01   1.38e-01 -2.44 0.01466
Classe_Funcional3  -1.31e-01   9.59e-02 -1.37 0.17075
Classe_Funcional4   6.76e+00   1.28e+03  0.01 0.99579
IAMSim             -2.99e-01   1.42e-01 -2.10 0.03544
RochaganSim         1.69e-01   1.28e-01  1.32 0.18849
Beta_BloqueadorSim -2.98e-01   9.32e-02 -3.20 0.00140
Anticorpos         -3.47e-03   1.93e-02 -0.18 0.85732
ECG_ResultMaiores  -5.85e-01   1.35e-01 -4.34 1.5e-05
Log(scale)         -8.13e-01   8.71e-02 -9.33 < 2e-16

Scale= 0.444 

Weibull distribution
Loglik(model)= -455.7   Loglik(intercept only)= -509.3
    Chisq= 107.34 on 10 degrees of freedom, p= 1.8e-18 
Number of Newton-Raphson Iterations: 17 
n= 744 
summary(m0ln)

Call:
survreg(formula = Surv(Tempo, Obito) ~ ., data = dados, dist = "lognormal")
                      Value Std. Error     z       p
(Intercept)          4.0190     0.3873 10.38 < 2e-16
SexoFeminino         0.4153     0.0983  4.23 2.4e-05
Idade               -0.0140     0.0040 -3.51 0.00044
Classe_Funcional2   -0.3325     0.1568 -2.12 0.03398
Classe_Funcional3   -0.1351     0.1033 -1.31 0.19107
Classe_Funcional4    3.7832   561.6280  0.01 0.99463
IAMSim              -0.1834     0.1831 -1.00 0.31659
RochaganSim          0.1555     0.1242  1.25 0.21061
Beta_BloqueadorSim  -0.3446     0.1048 -3.29 0.00100
Anticorpos          -0.0068     0.0206 -0.33 0.74117
ECG_ResultMaiores   -0.5413     0.1167 -4.64 3.5e-06
Log(scale)          -0.2626     0.0777 -3.38 0.00073

Scale= 0.769 

Log Normal distribution
Loglik(model)= -452.1   Loglik(intercept only)= -505.8
    Chisq= 107.48 on 10 degrees of freedom, p= 1.7e-18 
Number of Newton-Raphson Iterations: 13 
n= 744 

Foram observadas muitas variáveis não significativas nos modelos. Neste caso, opta-se por removê-las e usar somente as variáveis significativas:

m1ex <- survreg(Surv(Tempo, Obito) ~ Sexo+ Idade + Classe_Funcional +
                 Beta_Bloqueador + ECG_Result, 
               dist = "exponential", data = dados)
m1we <- survreg(Surv(Tempo, Obito) ~ Sexo + Idade + Classe_Funcional +
                 Beta_Bloqueador + ECG_Result, 
               dist = "weibull", data = dados)
m1ln <- survreg(Surv(Tempo, Obito) ~ Sexo+ Idade + Classe_Funcional +
                 Beta_Bloqueador + ECG_Result, 
               dist = "lognormal", data = dados)
summary(m1ex)

Call:
survreg(formula = Surv(Tempo, Obito) ~ Sexo + Idade + Classe_Funcional + 
    Beta_Bloqueador + ECG_Result, data = dados, dist = "exponential")
                       Value Std. Error     z       p
(Intercept)         6.60e+00   5.78e-01 11.42 < 2e-16
SexoFeminino        7.90e-01   1.97e-01  4.00 6.3e-05
Idade              -2.95e-02   7.91e-03 -3.74 0.00019
Classe_Funcional2  -7.61e-01   3.03e-01 -2.51 0.01196
Classe_Funcional3  -3.43e-01   2.11e-01 -1.62 0.10521
Classe_Funcional4   1.54e+01   3.40e+03  0.00 0.99639
Beta_BloqueadorSim -6.36e-01   2.03e-01 -3.14 0.00170
ECG_ResultMaiores  -1.34e+00   2.81e-01 -4.76 2.0e-06

Scale fixed at 1 

Exponential distribution
Loglik(model)= -489.9   Loglik(intercept only)= -537.9
    Chisq= 95.93 on 7 degrees of freedom, p= 7.5e-18 
Number of Newton-Raphson Iterations: 16 
n= 744 
summary(m1we)

Call:
survreg(formula = Surv(Tempo, Obito) ~ Sexo + Idade + Classe_Funcional + 
    Beta_Bloqueador + ECG_Result, data = dados, dist = "weibull")
                       Value Std. Error     z       p
(Intercept)         4.08e+00   3.16e-01 12.91 < 2e-16
SexoFeminino        3.74e-01   9.40e-02  3.98 7.0e-05
Idade              -1.34e-02   3.73e-03 -3.59 0.00033
Classe_Funcional2  -3.74e-01   1.39e-01 -2.69 0.00723
Classe_Funcional3  -1.66e-01   9.62e-02 -1.73 0.08447
Classe_Funcional4   6.85e+00   1.28e+03  0.01 0.99574
Beta_BloqueadorSim -3.02e-01   9.44e-02 -3.20 0.00138
ECG_ResultMaiores  -6.23e-01   1.37e-01 -4.56 5.2e-06
Log(scale)         -7.99e-01   8.71e-02 -9.17 < 2e-16

Scale= 0.45 

Weibull distribution
Loglik(model)= -458.6   Loglik(intercept only)= -509.3
    Chisq= 101.38 on 7 degrees of freedom, p= 5.6e-19 
Number of Newton-Raphson Iterations: 17 
n= 744 
summary(m1ln)

Call:
survreg(formula = Surv(Tempo, Obito) ~ Sexo + Idade + Classe_Funcional + 
    Beta_Bloqueador + ECG_Result, data = dados, dist = "lognormal")
                       Value Std. Error     z       p
(Intercept)          4.07778    0.30428 13.40 < 2e-16
SexoFeminino         0.42879    0.09804  4.37 1.2e-05
Idade               -0.01540    0.00396 -3.88 0.00010
Classe_Funcional2   -0.34909    0.15670 -2.23 0.02589
Classe_Funcional3   -0.16262    0.10251 -1.59 0.11263
Classe_Funcional4    3.79838  588.81314  0.01 0.99485
Beta_BloqueadorSim  -0.35836    0.10497 -3.41 0.00064
ECG_ResultMaiores   -0.56933    0.11665 -4.88 1.1e-06
Log(scale)          -0.25768    0.07777 -3.31 0.00092

Scale= 0.773 

Log Normal distribution
Loglik(model)= -453.5   Loglik(intercept only)= -505.8
    Chisq= 104.58 on 7 degrees of freedom, p= 1.2e-19 
Number of Newton-Raphson Iterations: 13 
n= 744 

Como os modelos conversam bastante sobre os parâmetros e suas significâncias, opta-se por se usar o Critério de Akaike para a seleção dos modelos.

extractAIC(m1ex)[2]
[1] 995.822
extractAIC(m1we)[2]
[1] 935.2947
extractAIC(m1ln)[2]
[1] 925.0673

O modelo log-normal apresenta um Akaike menor, sendo este o modelo paramétrico a ser adotado.

Modelo semi-paramétrico de Cox

Modelo de Cox:

mcox0 <- coxph(Surv(Tempo, Obito) ~ ., data = dados)
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights, :
Loglik converged before variable 5 ; coefficient may be infinite.
round(summary(mcox0)$coef, 2)
                     coef exp(coef) se(coef)     z Pr(>|z|)
SexoFeminino        -0.82      0.44     0.20 -4.08     0.00
Idade                0.03      1.03     0.01  3.14     0.00
Classe_Funcional2    0.75      2.11     0.31  2.44     0.01
Classe_Funcional3    0.26      1.30     0.21  1.23     0.22
Classe_Funcional4  -14.64      0.00  2198.72 -0.01     0.99
IAMSim               0.65      1.92     0.32  2.03     0.04
RochaganSim         -0.43      0.65     0.29 -1.48     0.14
Beta_BloqueadorSim   0.66      1.94     0.20  3.25     0.00
Anticorpos           0.01      1.01     0.04  0.26     0.79
ECG_ResultMaiores    1.30      3.68     0.28  4.61     0.00

Para o modelo de Cox, foi adicionado a variável IAM como significativa em relação aos demais modelos.

Novo ajuste apenas com as variáveis significativas:

mcox1 <- coxph(Surv(Tempo, Obito) ~ Idade + Sexo + Classe_Funcional + IAM +
                   Beta_Bloqueador + ECG_Result,
                 data = dados)
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights, :
Loglik converged before variable 5 ; coefficient may be infinite.
round(summary(mcox1)$coef, 2)
                     coef exp(coef) se(coef)     z Pr(>|z|)
Idade                0.03      1.03     0.01  3.51     0.00
SexoFeminino        -0.83      0.44     0.20 -4.14     0.00
Classe_Funcional2    0.77      2.16     0.30  2.52     0.01
Classe_Funcional3    0.30      1.34     0.21  1.38     0.17
Classe_Funcional4  -14.71      0.00  2270.71 -0.01     0.99
IAMSim               0.63      1.88     0.32  2.00     0.05
Beta_BloqueadorSim   0.68      1.97     0.20  3.33     0.00
ECG_ResultMaiores    1.35      3.86     0.28  4.81     0.00

Diagnóstico do modelo

O Modelo de Cox supõe proporcionalidade na razao de taxa de falhas. Checando se essa hipótese é válida para todas as todas as variaveis:

zph <- cox.zph(mcox1, transform = "identity")
zph
                   chisq df     p
Idade            0.58943  1 0.443
Sexo             0.00449  1 0.947
Classe_Funcional 0.74760  3 0.862
IAM              5.01049  1 0.025
Beta_Bloqueador  0.18019  1 0.671
ECG_Result       1.62525  1 0.202
GLOBAL           9.14641  8 0.330

Somente IAM foge da suposição sob um \(\alpha\) de 5%.

Ajuste do modelo removendo a covariável IAM:

mcox2 <- coxph(Surv(Tempo, Obito) ~ Idade + Sexo + Classe_Funcional +
                                    Beta_Bloqueador + ECG_Result,
                 data = dados)
Warning in coxph.fit(X, Y, istrat, offset, init, control, weights = weights, :
Loglik converged before variable 5 ; coefficient may be infinite.
round(summary(mcox2)$coef, 2)
                     coef exp(coef) se(coef)     z Pr(>|z|)
Idade                0.03      1.03     0.01  3.71     0.00
SexoFeminino        -0.86      0.43     0.20 -4.30     0.00
Classe_Funcional2    0.83      2.28     0.30  2.73     0.01
Classe_Funcional3    0.34      1.40     0.21  1.59     0.11
Classe_Funcional4  -14.64      0.00  2184.00 -0.01     0.99
Beta_BloqueadorSim   0.67      1.95     0.20  3.30     0.00
ECG_ResultMaiores    1.37      3.92     0.28  4.87     0.00
zph1 <- cox.zph(mcox2, transform = "identity")
zph1
                 chisq df    p
Idade            0.790  1 0.37
Sexo             0.016  1 0.90
Classe_Funcional 0.919  3 0.82
Beta_Bloqueador  0.169  1 0.68
ECG_Result       1.706  1 0.19
GLOBAL           3.446  7 0.84

Nesse caso, nenhuma das covariáveis fugiu da suposição de proporcionalidade na razao da taxa de falhas. Pode-se fazer uma análise de Schoenfeld para confirmar isto.

Avaliação dos resíduos de Schoenfeld

ggcoxzph(zph1[1:2])

ggcoxzph(zph1[3:4])

ggcoxzph(zph1[5])

A hipótese de proporcionalidade não é rejeitada, sendo este o modelo final de cox.

Discussões e Resultados:

Intervalos de Confiança e Interpretação de Coeficientes do Modelo de Cox:

coefcox <- as.data.frame(summary(mcox2)$coef)

coef_ic <- coefcox %>%
  transmute(
    lower_ci = exp(coef - 1.96 * `se(coef)`),
    hr = exp(coef),
    upper_ci = exp(coef + 1.96 * `se(coef)`),
    coef=coef
  )

print(round(coef_ic, 3))
                   lower_ci    hr upper_ci    coef
Idade                 1.014 1.030    1.046   0.029
SexoFeminino          0.288 0.425    0.628  -0.856
Classe_Funcional2     1.262 2.284    4.134   0.826
Classe_Funcional3     0.924 1.400    2.122   0.336
Classe_Funcional4     0.000 0.000      Inf -14.639
Beta_BloqueadorSim    1.312 1.955    2.911   0.670
ECG_ResultMaiores     2.262 3.920    6.794   1.366

Nesse caso, temos que, por exemplo, que o sexo feminino está relacionado com uma redução de risco de 57,5% de morte. Analisando a mesma variável no modelo lognormal:

cat("Coef:", summary(m1ln)$coef["SexoFeminino"], "\n")
Coef: 0.4287885 
cat("Exp Coef:", exp(summary(m1ln)$coef["SexoFeminino"]))
Exp Coef: 1.535396

Para o modelo lognormal o coeficiente da covariável idade foi POSITIVA, com sinal oposto ao modelo de COX. Isso acontece porque o modelo lognormal modela o TEMPO ATÉ A FALHA (no caso, morte do paciente chagásico). Ou seja, quanto maior a idade, menor o tempo até a morte do paciente. Já o modelo de COX modela o RISCO da falha. Isso é, quanto maior a idade, maior o RISCO de morte.

Para o modelo lognormal, pode ser interpretado em função de tempos medianos. Nesse caso sexo feminino está relacionado com um tempo mediano de falha 53% maior que sexo masculino. Isso é para o modelo de Cox, mulheres tem um risco menor de morte, para o modelo lognormal mulheres tem um tempo de vida mediano maior.