Gautret and colleagues have just published a paper on the efficacy of hydroxychloroquine (HCQ) and azithromycin in the treatment of patients infected with SARS-cov-2 (eg Wuhan virus). In the paper, the authors claim that “Despite its small sample size our survey shows that hydroxychloroquine treatment is significantly associated with viral load reduction/disappearance in COVID-19 patients and its effect is reinforced by azithromycin”. The paper can be accessed here:
https://www.sciencedirect.com/science/article/pii/S0924857920300996
I was curious of the conclusion and the idea underlying the study. The two drugs were not designed to treat SARS-cov-2 infection, so the hypothesis behind the study is quite novel. However, the more important question is whether the data are consistent with the authors’ conclusion. I noted that the authors have used a rather simple and descriptive analysis that did not account for the within-patient correlation. That is, to me, a significant weakness of the paper.
Therefore, I have downloaded and re-analyzed the data (which are provided in the paper’s appendix). My analysis is shown in this document. In contrast to the authors’ conclusion, I found no statistically significant effect of HCQ on the rate of negative tests.
library(lme4)
## Loading required package: Matrix
library(ggplot2)
library(gridExtra)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following object is masked from 'package:gridExtra':
##
## combine
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(table1)
##
## Attaching package: 'table1'
## The following objects are masked from 'package:base':
##
## units, units<-
library(epiDisplay)
## Loading required package: foreign
## Loading required package: survival
## Loading required package: MASS
##
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
##
## select
## Loading required package: nnet
##
## Attaching package: 'epiDisplay'
## The following object is masked from 'package:ggplot2':
##
## alpha
library(ICCbin) # for intraclass corr
library(sjPlot) # for model plotting
## #refugeeswelcome
df = read.csv("~/Dropbox/Bao chi/Coronavirus articles/HCQ study LONG.csv")
# recoding character variables
df$outc = ifelse(df$outcome=="NEG", 1, 0)
baseline = subset(df, time==0)
final = subset(df, time==6)
# Baseline data
table1(~age + sex + factor(outc) | hcq, data=baseline)
| No (n=16) |
Yes (n=20) |
Overall (n=36) |
|
|---|---|---|---|
| age | |||
| Mean (SD) | 37.4 (24.0) | 51.2 (18.7) | 45.1 (22.0) |
| Median [Min, Max] | 32.5 [10.0, 75.0] | 51.5 [20.0, 87.0] | 47.0 [10.0, 87.0] |
| sex | |||
| F | 10 (62.5%) | 11 (55.0%) | 21 (58.3%) |
| M | 6 (37.5%) | 9 (45.0%) | 15 (41.7%) |
| factor(outc) | |||
| 0 | 16 (100%) | 20 (100%) | 36 (100%) |
table1(~age + sex + factor(outc) | azi, data=baseline)
| No (n=30) |
Yes (n=6) |
Overall (n=36) |
|
|---|---|---|---|
| age | |||
| Mean (SD) | 44.6 (23.4) | 47.5 (14.1) | 45.1 (22.0) |
| Median [Min, Max] | 44.5 [10.0, 87.0] | 51.5 [20.0, 60.0] | 47.0 [10.0, 87.0] |
| sex | |||
| F | 19 (63.3%) | 2 (33.3%) | 21 (58.3%) |
| M | 11 (36.7%) | 4 (66.7%) | 15 (41.7%) |
| factor(outc) | |||
| 0 | 30 (100%) | 6 (100%) | 36 (100%) |
# Day 6 data
table1(~age + sex + factor(outc) | hcq, data=final)
| No (n=16) |
Yes (n=20) |
Overall (n=36) |
|
|---|---|---|---|
| age | |||
| Mean (SD) | 37.4 (24.0) | 51.2 (18.7) | 45.1 (22.0) |
| Median [Min, Max] | 32.5 [10.0, 75.0] | 51.5 [20.0, 87.0] | 47.0 [10.0, 87.0] |
| sex | |||
| F | 10 (62.5%) | 11 (55.0%) | 21 (58.3%) |
| M | 6 (37.5%) | 9 (45.0%) | 15 (41.7%) |
| factor(outc) | |||
| 0 | 14 (87.5%) | 7 (35.0%) | 21 (58.3%) |
| 1 | 2 (12.5%) | 13 (65.0%) | 15 (41.7%) |
table1(~age + sex + factor(outc) | azi, data=final)
| No (n=30) |
Yes (n=6) |
Overall (n=36) |
|
|---|---|---|---|
| age | |||
| Mean (SD) | 44.6 (23.4) | 47.5 (14.1) | 45.1 (22.0) |
| Median [Min, Max] | 44.5 [10.0, 87.0] | 51.5 [20.0, 60.0] | 47.0 [10.0, 87.0] |
| sex | |||
| F | 19 (63.3%) | 2 (33.3%) | 21 (58.3%) |
| M | 11 (36.7%) | 4 (66.7%) | 15 (41.7%) |
| factor(outc) | |||
| 0 | 21 (70.0%) | 0 (0%) | 21 (58.3%) |
| 1 | 9 (30.0%) | 6 (100%) | 15 (41.7%) |
# Logistic regression analysis
m6 = glm(outc ~ age + sex + hcq, family=binomial, data=final)
logistic.display(m6)
##
## Logistic regression predicting outc
##
## crude OR(95%CI) adj. OR(95%CI) P(Wald's test)
## age (cont. var.) 1 (0.97,1.03) 0.97 (0.93,1.02) 0.252
##
## sex: M vs F 2.29 (0.59,8.91) 1.72 (0.29,10.07) 0.547
##
## hcq: Yes vs No 13 (2.27,74.32) 24.17 (2.66,219.27) 0.005
##
## P(LR-test)
## age (cont. var.) 0.228
##
## sex: M vs F 0.545
##
## hcq: Yes vs No < 0.001
##
## Log-likelihood = -17.6083
## No. of observations = 36
## AIC value = 43.2166
m7 = glm(outc ~ age + sex + hcq + azi, family=binomial, data=final)
logistic.display(m7)
##
## Logistic regression predicting outc
##
## crude OR(95%CI) adj. OR(95%CI) P(Wald's test)
## age (cont. var.) 1 (0.97,1.03) 0.97 (0.92,1.02) 0.264
##
## sex: M vs F 2.29 (0.59,8.91) 1.18 (0.17,8.37) 0.868
##
## hcq: Yes vs No 13 (2.27,74.32) 12.8 (1.38,118.52) 0.025
##
## azi: Yes vs No 269847183.02 (0,Inf) 98615039.9 (0,Inf) 0.994
##
## P(LR-test)
## age (cont. var.) 0.239
##
## sex: M vs F 0.868
##
## hcq: Yes vs No 0.01
##
## azi: Yes vs No 0.018
##
## Log-likelihood = -14.8101
## No. of observations = 36
## AIC value = 39.6202
# Estimate intraclass correlation
iccbin(cid = id, y = outc, data = df)
## Warning in iccbin(cid = id, y = outc, data = df): 'cid' has been coerced to
## a factor
## Warning in iccbin(cid = id, y = outc, data = df): One or Both of 'Zou and
## Donner's Modified Wald' Confidence Limits Fell Outside of [0, 1]
## Warning in iccbin(cid = id, y = outc, data = df): ICC Not Estimable by
## 'Moment with Equal Weights' Method
## Warning in iccbin(cid = id, y = outc, data = df): ICC Not Estimable by
## 'Moment with Weights Proportional to Cluster Size' Method
## Warning in iccbin(cid = id, y = outc, data = df): ICC Not Estimable by
## 'Modified Moment with Equal Weights' Method
## Warning in iccbin(cid = id, y = outc, data = df): ICC Not Estimable by
## 'Modified Moment with Weights Proportional to Cluster Size' Method
## $estimates
## Methods
## 1 ANOVA Estimate
## 2 Modified ANOVA Estimate
## 3 Moment Estimate with Equal Weights
## 4 Moment Estimate with Weights Proportional to Cluster Size
## 5 Modified Moment Estimate with Equal Weights
## 6 Modified Moment Estimate with Weights Proportional to Cluster Size
## 7 Stabilized Moment Estimate
## 8 Moment Estimate from Unbiased Estimating Equation
## 9 Fleiss-Cuzick Kappa Type Estimate
## 10 Mak's Unweighted Average Estimate
## 11 Correlation Estimate with Equal Weight to Every Pair of Observations
## 12 Correlation Estimate with Equal Weight to Each Cluster Irrespective of Size
## 13 Correlation Estimate with Weighting Each Pair According to Number of Pairs individuals Appear
## 14 Resampling Estimate
## 15 First-order Model Linearized Estimate
## 16 Monte Carlo Simulation Estimate
## ICC
## 1 0.370367364605313
## 2 0.362212276214834
## 3 -
## 4 -
## 5 -
## 6 -
## 7 0.452323103154305
## 8 0.370367364605313
## 9 0.362212276214834
## 10 0.370367364605313
## 11 0.362212276214834
## 12 0.362212276214834
## 13 0.362212276214834
## 14 0.375164413591523
## 15 0.354513821810483
## 16 0.398377088822742
##
## $ci
## Type LowerCI UpperCI
## 1 Smith's Large Sample Confidence Interval 0.2239875 0.5167472
## 2 Zou and Donner's Modified Wald Confidence Interval 0.0000000 1.0000000
## 3 Fleiss-Cuzick Confidence Interval 0.1505523 0.5738722
## 4 Pearson Correlation Type Confidence Interval 0.1473362 0.5770884
## 5 Resampling Based Confidence Interval 0.1567670 0.5935618
m1 = glmer(outc ~ hcq + azi + time + hcq:time + azi:time + (1 | id), data=df, family = binomial(link="logit"))
summary(m1)
## Generalized linear mixed model fit by maximum likelihood (Laplace
## Approximation) [glmerMod]
## Family: binomial ( logit )
## Formula: outc ~ hcq + azi + time + hcq:time + azi:time + (1 | id)
## Data: df
##
## AIC BIC logLik deviance df.resid
## 174.4 199.1 -80.2 160.4 245
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.6858 -0.1877 -0.0817 0.0055 2.6802
##
## Random effects:
## Groups Name Variance Std.Dev.
## id (Intercept) 8.131 2.851
## Number of obs: 252, groups: id, 36
##
## Fixed effects:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -5.8256 1.5973 -3.647 0.000265 ***
## hcqYes 1.7120 1.7309 0.989 0.322641
## aziYes -5.0888 3.4290 -1.484 0.137800
## time 0.4545 0.2288 1.986 0.047007 *
## hcqYes:time 0.2753 0.2980 0.924 0.355525
## aziYes:time 2.7199 1.1586 2.348 0.018897 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) hcqYes aziYes time hcqYs:
## hcqYes -0.713
## aziYes 0.098 -0.240
## time -0.668 0.550 -0.031
## hcqYes:time 0.366 -0.625 0.110 -0.721
## aziYes:time -0.188 0.126 -0.871 0.060 -0.076
# Plotting data
summary.data = df %>% group_by(hcq, time) %>% summarise(outcome=mean(outc))
p1 = plot_model(m1, type="pred", terms = c("time", "hcq"))
p1 = p1 + geom_point(data=summary.data, aes(x=time, y=outcome, color=hcq), inherit.aes = F) + scale_color_manual(values=c("blue", "red")) + scale_fill_manual(values=c("blue", "red")) + labs(title="Effect of hydroxychloroquine", x="Day", y="Probability of Negative Tests") + theme(legend.position="top")
## Scale for 'colour' is already present. Adding another scale for
## 'colour', which will replace the existing scale.
## Scale for 'fill' is already present. Adding another scale for 'fill',
## which will replace the existing scale.
summary.data = df %>% group_by(azi, time) %>% summarise(outcome=mean(outc))
p2 = plot_model(m1, type="pred", terms = c("time", "azi"))
p2 = p2 + geom_point(data=summary.data, aes(x=time, y=outcome, color=azi), inherit.aes = F) + scale_color_manual(values=c('blue','red')) + scale_fill_manual(values=c('blue','red')) + labs(title="Effect of azithromycin", x="Day", y="Probability of Negative Tests") + theme(legend.position="top")
## Scale for 'colour' is already present. Adding another scale for
## 'colour', which will replace the existing scale.
## Scale for 'fill' is already present. Adding another scale for 'fill',
## which will replace the existing scale.
grid.arrange(p1, p2, ncol=2)