臨床に直結しそうで、ベッドサイドですぐに役立ちそうなエビデンスはぶっちゃけ書かれていない

読んだ。

COI:定価は高かったけど古くなったのでくっそ安くなってたので買って積んでた。読み始めてから知り合いがいたことに気づいた。

インテンシヴィストっぽく、とあるテーマにたいして文献検索してPICOをまとめただけの本。解説部分と文献検索結果がだいたい7:3くらいな感じなので、500Pくらいあるけど実質300Pくらい。
ほとんどの項目が強固なエビデンスでやったほうがよい、というものではないので、結局その病院ルールで治療が進みそうな感じがする。集中治療しらんけど。

EGDTについて
www.ncbi.nlm.nih.gov
の論文、本文中では

主要アウトカムの病院内死亡率がScvO2 群で23%(95% CI 17-30%)、乳酸群で17%(95% CI 11-24%)であり、統計学的に差がないことが証明された。

と書いているが、統計学的に差がないことを証明する統計学手法があったらぜひ知りたい、と思って原著を確認すると

OBJECTIVE: To test the hypothesis of noninferiority between lactate clearance and central venous oxygen saturation (ScvO2) as goals of early sepsis resuscitation.

10%マージンを設定した非劣性デザインであって

CONCLUSION: Among patients with septic shock who were treated to normalize central venous and mean arterial pressure, additional management to normalize lactate clearance compared with management to normalize ScvO2 did not result in significantly different in-hospital mortality.

乳酸クリアランスを指標にしたショック管理はCVとってScvO2 を管理するのとは、統計学的には有意な差にはならなかった、と言っているだけであって、差がない、とは言ってないので解散。

薦める理由がない。

こういうことだったようだ:酸素療法・NPPV・CHDF

読んだ。

こういうことだったのか!! 酸素療法

こういうことだったのか!! 酸素療法

  • 作者:小尾口 邦彦
  • 発売日: 2017/04/19
  • メディア: 単行本(ソフトカバー)
こういうことだったのか!! NPPV

こういうことだったのか!! NPPV

  • 作者:小尾口 邦彦
  • 発売日: 2017/07/19
  • メディア: 単行本(ソフトカバー)
こういうことだったのか!! CHDF

こういうことだったのか!! CHDF

  • 作者:小尾口 邦彦
  • 発売日: 2018/02/27
  • メディア: 単行本(ソフトカバー)
COI:職場にあった。

各々2000円くらいなので買ってもまあよい。
麻酔的には馴染みがないが、ICU的にはよく使われる医療機器。
生理学的なところの整理と、医療機器の説明があったのでそれなりにわかりやすいとは思う。
ただし、実際の患者にどう使うか(使ったか)というのはあまり記載がないので、そこらへんを知りたいなら意識高くインテンシヴィストを読まなければならないかもしれない。

令和2年2月21日版の国内コロナ陽性者をgooglevisでやる

www.mhlw.go.jp
これの2月20日12:00現在、確認されている国内の発生状況の国内事例(チャーター便帰国者を除く)をgooglevisを使って都道府県名別にプロットしてみようと思った。
googlevisクッソ使いにくい。
厚生労働省もがんばっているのだろうが、(令和2年2月21日版)って半角と全角が入り乱れいるのが謎だし、2月19日版のデータでは年齢が40<代っていう人がいてがんばれ、って思った。




GeoChartID1d202ba7ad88






新型コロナウイルス感染症の現在の状況と厚生労働省の対応について(令和2年2月21日版)

library(googleVis)
library(stringr)
library(textreadr)
url <- "https://www.mhlw.go.jp/stf/newpage_09670.html" # 2月19日版
url <- "https://www.mhlw.go.jp/stf/newpage_09690.html" # 2月21日版
webpage <-"新型コロナウイルス感染症の現在の状況と厚生労働省の対応について(令和2年2月21日版)" 
html <- read_html(url)

idx0 <- which(str_detect(html, "確認されている国内の発生状況は以下のとおり"))
idx1 <- which(str_detect(html, "2/19"))
idx2 <- which(str_detect(html, "男|女"))
idx3 <- which(str_detect(html, "武漢市からのチャーター便帰国者に係る発生状況"))
idx4 <- idx2[idx2 < idx3]

pref <- c('北海道','青森','岩手','宮城','秋田','山形','福島','茨城','栃木','群馬','埼玉','千葉','東京','神奈川','新潟','富山','石川','福井','山梨','長野','岐阜','静岡','愛知','三重','滋賀','京都','大阪','兵庫','奈良','和歌山','鳥取','島根','岡山','広島','山口','徳島','香川','愛媛','高知','福岡','佐賀','長崎','熊本','大分','宮崎','鹿児島','沖縄')

mat <- matrix(html[c(t(outer(idx4, -2:1, "+")))], nc=4, byrow=TRUE)
colnames(mat) <- c("date", "age", "sex", "pref")
mat[,4] <- str_replace(mat[,4], "[都道府県]$", "")
mat[,4] <- ifelse(mat[,4]=="中国", NA, mat[,4])
mat <- as.data.frame(mat)
mat[,1] <- as.Date(sprintf("2020/%s", mat[,1]))

dat <- data.frame(table(factor(mat$pref, pref)))
colnames(dat) <- c("pref", "N")

opt <- list(region="JP", displayMode="regions", resolution="provinces", height=500, width=800)
c0 <- seq(min(dat$N), max(dat$N), length=7)
opt$colorAxis <- paste0("{values:[", paste(c0, collapse=","), "], colors:['", paste0(matlab::jet.colors(length(c0)), collapse="','"), "']}", collapse="")

gmap <- gvisGeoChart(dat, "pref", "N", options=opt)
gmap$html$caption <- NULL
#gmap$html$footer <- NULL
gmap$html$footer <- str_replace(gmap$html$footer,
                                "<span> \n  .+\n  .+\n</span>",
                                sprintf("<span><a href='%s' target='_blank'>%s</a></span>", url, webpage))
plot(gmap)

ふたつの分布が違うのをどう示したらいいですか

こんな記事を書いたらごく狭い範囲で反響があった。
mikuhatsune.hatenadiary.com

ガンマ分布の記事感動しました。そんなガンマ分布マスターであられる驚異のアニヲタさんに質問するもの失礼なほど低級な質問なんですが、、、
ある疾患があって、その疾患発症間隔(病院で観察された日)の分布が院外と院内で違う、ということを言いたいようです。ブログの例の混合分布ではなく、すでに違う二つの群のデータが取れているとして、その群に差がある、と言いたいと。
この場合は指数分布というかガンマ分布で回帰して係数がちがうと言えばいいでのでしょうか?

と聞かれたので考えた。

群が分かれている、ということなので、分布を近似してその近似した分布を決定しているパラメータを比較するか、分布を近似せずにそのまま扱うリサンプリングな方法で考えればいいのではないだろうか。
データはシミュレーション用に再度、ガンマ分布から適当に生成されたとする。

f:id:MikuHatsune:20200219225658p:plain
データを生成するガンマ分布
シミュレーションデータはこんな感じになる。30症例ずつくらいにしている。
f:id:MikuHatsune:20200219225800p:plain
シミュレーションデータ

リサンプリングでやってみる。
いま手元にあるデータから経験分布を作って、そのデータと同じ数だけ重複を許して、すなわちreplace=TRUE でいくつも偽データセットを作る。
その上で平均(分布が非対称ならmedian なり適当な要約量を使う)をとり、平均の差分が0なら一緒(みたいなもの)とみなし、差があれば違う、と考える。0をまたぐ偽セット数をp値っぽいものとみなす。
今回のシミュレーション結果としては

    2.5%      50%    97.5% 
4.842738 6.521429 8.466786 

となり、すべて平均の差が0以上なので、ふたつの分布は違うのだろう、と判断する。

stanでやってみる。
分布がなんらかのパラメータを持つ有名な分布で近似できるならば、そのパラメータを推定しにかかる。パラメータから平均など要約量が取れるので、それを比較する。
f:id:MikuHatsune:20200219231720p:plain
パラメータ推定値から計算した平均値の差は、リサンプリングでしたときとだいたい同じになる。これもふたつの分布は違うようだ、と判断する。

    2.5%      50%    97.5% 
4.704524 6.579990 8.594733 

f:id:MikuHatsune:20200219231704p:plain

検定的に考えるならこんな感じだが、分布の形を平均値というひとつの統計量にして、しかもその平均値が分布をよく表すとも言えないのにこれでなんとかするのはどうなんだろうかと思う。
少し前に別の人に聞かれて、分布の形があるならカルバックライブラーとかJensen-Shannon とかどう?って返答したことがあるが、これを使った検定はあるのだろうか(適当

shape <- c(4, 6)
scale <- c(1.4, 2)
x <- 0:30
lab <- c("院内", "院外")
n <- c(30, 35)

d <- t(mapply(dgamma, x, list(shape), scale=list(scale)))
cols <- c("blue", "orange")
matplot(x, d, type="l", lty=1, col=cols, lwd=3, xlab="発症までの日数", ylab="確率密度")
legend("topright", legend=lab, lty=1, col=cols, lwd=3)

dat <- lapply(mapply(rgamma, n, shape, scale=scale, SIMPLIFY=FALSE), floor)
day_max <- max(unlist(dat))
b <- sapply(lapply(dat, factor, 0:day_max), table)

barplot(t(b), beside=TRUE, col=cols, xlab="発症までの日数", ylab="発症人数")
legend("topright", legend=lab, pch=15, col=cols, cex=2)

alpha <- 0.05
# リサンプリング
niter <- 1000
diff_mean <- replicate(niter, diff(sapply(mapply(sample, dat, replace=TRUE), mean)))
hist(diff_mean, xlab="発症までの平均日数差分 [院外 - 院内]", main="リサンプリング", nclass=30)
quantile(diff_mean, c(alpha/2, 0.5, 1-alpha/2))

# stan
library(rstan)
library(vioplot)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())

code <- "
  data {
    int<lower=0> N1;
    real<lower=0> y1[N1];
    int<lower=0> N2;
    real<lower=0> y2[N2];
  }

  parameters {
  real<lower=0, upper=20> shape[2];
  real<lower=0, upper=10> scale[2];
  }

  model {
    y1 ~ gamma(shape[1], 1/scale[1]);
    y2 ~ gamma(shape[2], 1/scale[2]);
  }
"

m0 <- stan_model(model_code=code)
standata <- list(N1=n[1], y1=dat[[1]], N2=n[2], y2=dat[[2]])
fit <- sampling(m0, standata, iter=1000, warmup=300, chain=4)
ex <- extract(fit, pars=c("shape", "scale"))
mes <- ex$shape * ex$scale

vdat <- cbind.data.frame(estimate=c(mes), label=rep(lab, each=nrow(mes)))
vioplot(estimate ~ label, data=vdat, col=rev(cols), horizontal=TRUE, at=c(1, 2), side="right",
        xlab="発症までの平均日数", ylab="")
abline(h=c(1, 2), lty=3)
abline(v=shape*scale, lty=3)

apply(mes, 2, quantile, c(alpha/2, 0.5, 1-alpha/2))
diff_mes <- apply(mes, 1, diff)
quantile(diff_mes, c(alpha/2, 0.5, 1-alpha/2))
hist(diff_mes, xlab="発症までの平均日数差分 [院外 - 院内]", main="リサンプリング", nclass=30)


diff_true <- diff(shape*scale)
par(mfrow=c(1, 2))
hist(diff_mean, xlab="発症までの平均日数差分 [院外 - 院内]", main="リサンプリング", nclass=30)
abline(v=diff_true, lwd=3, col="red")
hist(diff_mes, xlab="発症までの平均日数差分 [院外 - 院内]", main="Rstan", nclass=30)
abline(v=diff_true, lwd=3, col="red")

混合分布:急性期と亜急性期の発症がある

こんな感じのデータを見かけた。
f:id:MikuHatsune:20200216160928p:plain
本人が言うには、8日あたりを境目にして、8日までに発症するパターンと、それ以降に発症するパターンに分かれそうだ、という。
データの意味合いと二峰性の具合から、おそらくふたつのガンマ分布が重なっているのでは、という気分になった。というのも、横軸が0以上の整数値となる分布はガンマ分布で、混合分布として解けばたぶんふたつのパターンがそもそもどれくらいの存在比で、かつ、ガンマ分布で近似されれば各々のパターンの発症平均日が推定できるので、たぶんうれしい。
実際のデータはこの2つの分布から生成された。
f:id:MikuHatsune:20200216161003p:plain

というわけで混合分布の推定で調べると、よく出てくるのはEMアルゴリズムベイズである。

グダグダやっていたのだが、Rのパッケージで簡単にガンマ分布(というか混合分布全般)をEMアルゴリズムで推定してくれるやつがあった。
kusanagi.hatenablog.jp
mixtools というパッケージ内でgammamixEM という関数がEMアルゴリズムをしているが、ベクトル化されていない部分があるので適当にパクって書き換える。

初期値の設定としては、混合比\lambda はディリクレ分布から適当に拾う。一様分布でも0.5 ずつでもよい。
ガンマ分布にはshape と呼ばれる\alpha と、scale もしくはrate (shape の逆数)と呼ばれる\beta のパラメータを設定する必要がある。mixtools では初期値の設定として、観測データx があるときにx をsort したあとに均等に分割して、分割されたセットx_i に対して(以下では表記の簡便化のためxx_i のつもりである)
E(x)=\bar{x}
E(x^2)=\bar{x^2}
\alpha \gets \frac{\bar{x}^2}{\bar{x^2}-\bar{x}^2}
\frac{1}{\beta} \gets \frac{\bar{x}^2}{\bar{x^2}-\bar{x}^2}\alpha の分母と表記を合わせるために逆数にしてrate 表記にしている)
と設定している。
パラメータの収束は以下のようになる。ふたつの分布があるという前提なので、初期値次第では入れ替わる。この場合は色が入れ替わっているのでそうなる。
f:id:MikuHatsune:20200216162211p:plain

パラメータ的にはずれているように見えるが、分布としてしまうと意外とずれていないようである。
f:id:MikuHatsune:20200216162502p:plain

このときのパラメータの推定値は

$shape
[1] 3.859840 9.942502

$rate
[1] 0.7749822 0.5455689

$pi
[1] 0.3431185 0.6568815

それぞれの分布の平均値は

[1]  4.980553 18.224100

同様のことをstanを使ってやっている。
stan のマニュアルにゼロ過剰モデルなどあるので、いろいろパクってgamma_lpdf でtarget すると書ける。
stan でやるとパラメータの点推定値のみが得られるが、サンプリング過程でたくさんのパラメータを得るので、信頼区間(というか信用区間だが)っぽいものも得られる。
バイオリンプロットのside パラメータで片側だけ描けるというのがわかった。かつての努力はいったいなんだったんだろうか。
mikuhatsune.hatenadiary.com
f:id:MikuHatsune:20200216163854p:plain

このときのパラメータの推定値は

$shape
[1] 3.896891 9.949112

$rate
[1] 0.7848028 0.5453568

$pi
[1] 0.3413815 0.6586185

それぞれの分布の平均値は

[1]  4.96544 18.24331

発症する平均日数の95%信用区間

            [,1]     [,2]
  2.5%  4.453284 17.54821
  97.5% 5.559489 18.79025

ただし、stanでやるときは複数のchain を使うと、分布が入れ替わる問題で\hat{R} が収束しないのでchain はひとつでやった。解決策を知りたい。

# シミュレーションデータの準備
x <- 0:100
n <- 1000
shape <- c(4, 9)
rate <- c(0.8, 0.5)
mix <- c(1, 2)
p <- mix/sum(mix)
d <- t(mapply(dgamma, x, list(shape), list(rate)) * p)
xl <- c(0, 50)
cols <- c("blue", "orange")
par(mar=c(4.5, 5, 2, 2))
matplot(x, d, type="l", xlim=xl, xlab="発症までの日数", ylab="確率密度", col=cols, lty=1, lwd=3)
# 実際に得たデータ
y <- sample(x, size=n, prob=rowSums(d), replace=TRUE)

# EM アルゴリズムを行う関数
EMgamma <- function(dat, k=2, eps=10e-8, maxit=300, seed=1234){
  set.seed(seed)
  idx <- sample(1:k, size=length(dat), replace=TRUE)
  x.bar <- tapply(dat, idx, mean)
  x2.bar <- tapply(dat^2, idx, mean)
  theta <- thetas <- c(x.bar^2/(x2.bar - x.bar^2), (x2.bar - x.bar^2)/x.bar)
  lambda <- lambdas <- c(MCMCpack::rdirichlet(1, rep(1, k)))
  dens1 <- t(mapply(dgamma, dat, shape=list(head(theta, k)), scale=list(tail(theta, k))) * lambda)
  ll <- old.obs.ll <- sum(log(rowSums(dens1)))
  gamma.ll <- function(theta, z, lambda, k) -sum(z * log(t(mapply(dgamma, dat, shape=list(head(theta, k)), scale=list(tail(theta, k))) * lambda)), na.rm=FALSE)
  diff <- 1 + eps
  iter <- 0
  while (diff > eps && iter < maxit) {
    # M step
    dens1 <- t(mapply(dgamma, dat, shape=list(head(theta, k)), scale=list(tail(theta, k))) * lambda)
    z <- dens1/rowSums(dens1)
    lambda.hat <- colMeans(z)
    out <- try(suppressWarnings(nlm(gamma.ll, p = theta, lambda = lambda.hat, k = k, z = z)), silent = TRUE)
    theta.hat <- out$estimate
    new.obs.ll <- sum(log(colSums(mapply(dgamma, dat, shape=list(head(theta.hat, k)), scale=list(tail(theta.hat, k))) * lambda.hat)))
    diff <- new.obs.ll - old.obs.ll
    old.obs.ll <- new.obs.ll
    ll <- c(ll, old.obs.ll)
    # E step
    lambda <- lambda.hat
    lambdas <- rbind(lambdas, lambda)
    theta <- theta.hat
    thetas <- rbind(thetas, theta)
    iter <- iter + 1
  }
  rownames(thetas) <- NULL
  rownames(lambdas) <- NULL
  return(list(theta=thetas, theta.hat=theta.hat, lambda=lambdas, lambda.hat=lambda.hat, ll=ll))
}

out <- EMgamma(y, k=2, seed=123)

lwd <- 3
par(mfcol=c(3, 1), mar=c(1, 5, 1.2, 1), cex.lab=2, cex.axis=1.5, las=1)
# 混合比
matplot(out$lambda, type="l", lty=1, col=cols, xlab="iteration", ylab=expression(lambda), ylim=c(0, 1), lwd=lwd)
abline(h=p, lty=3, col=cols, lwd=lwd)
# shape parameter
par(mar=c(1, 5, 1.2, 1))
matplot(out$theta[, 1:k], type="l", lty=1, ylab="shape parameter", col=cols, lwd=lwd)
abline(h=shape, lty=3, col=cols, lwd=lwd)
# scale (rate)  parameter
par(mar=c(4.5, 5, 1.2, 1))
matplot(out$theta[, (2*k-1):(2*k)], type="l", xlab="iteration", ylab="scale parameter", lty=1, col=cols, lwd=lwd)
abline(h=1/rate, lty=3, col=cols, lwd=lwd)

# EM でのプロット
pars <- list(shape=head(out$theta.hat, 2), rate=1/tail(out$theta.hat, 2), pi=out$lambda.hat)
pars$rate <- switch(which.min(pars$shape), pars$rate, rev(pars$rate))
pars$pi <- switch(which.min(pars$shape), pars$pi, rev(pars$pi))
pars$shape <- switch(which.min(pars$shape), pars$shape, rev(pars$shape))
est <- t(mapply(dgamma, x, list(pars$shape), list(pars$rate)) * pars$pi)

alpha <- 0.05
me <- pars$shape / pars$rate

library(vioplot)
b <- table(factor(y, x))
par(mar=c(4.5, 5, 4, 2), las=1, cex.lab=2)
plot(b, xlim=xl, xlab="発症までの日数", ylab="発症人数",)
for(i in  seq(ncol(est))){
  lines(x, est[, i]*n, lwd=3, col=cols[i])
  lines(x, d[, i]*n, lwd=3, col=cols[i], lty=3)
}
axis(3, at=me, labels=sprintf("%.1f days", me), padj=0)
text(par()$usr[1], par()$usr[4], "発症平均日数", xpd=TRUE, pos=3, cex=1.2)
legend("topright", legend=c("真の値", "推定値"), lty=c(1, 3), lwd=3, cex=1.5)

# stan を使った推定
library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())

code <- "
  data {
    int<lower=0> N;
    int<lower=0> y[N];
  }

  parameters {
  real<lower=0, upper=20> shape[2];
  real<lower=0, upper=10> rate[2];
  simplex[2] pi;
  }

  model {
    real ps[2];
    for(n in 1:N){
      ps[1] = log(pi[1]) + gamma_lpdf(y[n] | shape[1], rate[1]);
      ps[2] = log(pi[2]) + gamma_lpdf(y[n] | shape[2], rate[2]);
      target += log_sum_exp(ps);
    }
  }
"

m0 <- stan_model(model_code=code)
standata <- list(N=length(y), y=y)
fit <- sampling(m0, standata, iter=1000, warmup=300, chain=1)
ex <- extract(fit, pars=c("shape", "rate", "pi"))
mes <- ex$shape/ex$rate
pars <- lapply(ex, apply, 2, median)
pars$rate <- switch(which.min(pars$shape), pars$rate, rev(pars$rate))
pars$pi <- switch(which.min(pars$shape), pars$pi, rev(pars$pi))
mes <- switch(which.min(pars$shape), mes, mes[,rev(seq(ncol(mes)))])
pars$shape <- switch(which.min(pars$shape), pars$shape, rev(pars$shape))
est <- t(mapply(dgamma, x, list(pars$shape), list(pars$rate)) * pars$pi)

alpha <- 0.05
me <- pars$shape / pars$rate
mes.ci <- apply(mes, 2, quantile, c(alpha/2, 1-alpha/2))  

# バイオリンプロットも使って発症する平均日数の分布もプロットする
library(vioplot)
b <- table(factor(y, x))
par(mar=c(4.5, 5, 4, 2), las=1, cex.lab=2)
plot(b, xlim=xl, xlab="発症までの日数", ylab="発症人数",)
for(i in  seq(ncol(est))){
  lines(x, est[, i]*n, lwd=3, col=cols[i])
  lines(x, d[, i]*n, lwd=3, col=cols[i], lty=3)
  vioplot(mes[,i], at=par()$usr[4], add=TRUE, horizontal=TRUE, wex=10,
          rectCol=NA, colMed=NA, lineCol=NA,
          side="right", xpd=TRUE, col=cols[i])
}
# axis(3, at=me, labels=sprintf("%.1f days", me), padj=-1)
axis(3, at=me, labels=sprintf("%.1f days\n[%.1f, %.1f]", me, mes.ci[1,], mes.ci[2,]), padj=-0.5)
text(par()$usr[1], par()$usr[4], "発症平均日数", xpd=TRUE, pos=3, cex=1.2)
legend("topright", legend=c("真の値", "推定値"), lty=c(1, 3), lwd=3, cex=1.5)

モニター

読んだ。

INTENSIVIST Vol.3 No.2 2011(特集:モニター)

INTENSIVIST Vol.3 No.2 2011(特集:モニター)

  • 作者:
  • 出版社/メーカー: メディカルサイエンスインターナショナル
  • 発売日: 2011/04/14
  • メディア: 単行本
COI:昔安かったのを買って積んでた。

右心カテーテルと動脈血圧モニターの話がそれなりに分かった。
結局PACは心臓手術だとルーチンで入れてしまっているが、PA圧が30超えるかくらいしか興味がないし、せっかくCIやCVPが出るならちゃんとSVRを計算して血管抵抗も考えようと思った。
安かったら買いだと思う。

ICUルーチン

読んだ。

INTENSIVIST Vol.6 No.2 2014 (特集:ICUルーチン)

INTENSIVIST Vol.6 No.2 2014 (特集:ICUルーチン)

  • 作者:
  • 出版社/メーカー: メディカルサイエンスインターナショナル
  • 発売日: 2014/04/14
  • メディア: 単行本(ソフトカバー)
COI:安くなってて買ったのを積んでた。

胸部X線を毎朝撮るのとか、PPIをとりあえずいれておくのがどんだけ意味があるのかと思って読んだ。あまり意味がなさそう。
テーマがペラいのでページ数もペラかった。静脈ルート問題とか、バーンアウト問題とかそういうのも載っていたので少し参考になった。
読むべきかと聞かれたら必ずしもそうでもないと思う。