/ R, k: S5 W" ~0 [ ?7 U 净重新分类指数NRI的计算& v G2 ?! N: C. o C
“ 医学和生信笔记,专注R语言在临床医学中的使用,R语言数据分析和可视化。主要分享R语言做医学统计学、meta分析、网络药理学、临床预测模型、机器学习、生物信息学等。 : j9 U* T( S+ u. aNRI,net reclassification index,净重新分类指数,是用来比较模型准确度的,这个概念有点难理解,但是非常重要,在临床研究中非常常见,是评价模型的一大利器! 9 h* ]1 v2 [# E' M" j1 b# N) O, n0 f9 t4 B; J( q- E# S6 [; B+ J6 W
在R语言中有很多包可以计算NRI,但是能同时计算logistic回归和cox回归的只有nricens包,PredictABEL可以计算logistic模型的净重分类指数,survNRI可以计算cox模型的净重分类指数。 + y6 n8 y: K" t% c2 f4 L; O1 a& Q0 Y1 u4 @) q% A
logistic的NRI3 \1 `, {# | S3 t' c+ f
nricens包1 J: c3 n2 b: E: [
PredictABEL包 " U6 m' w$ r& }- H0 |- N( b生存分析的NRI( P) e6 ?2 m0 x, m: L
nricens包 - Y2 \- j, R( d" P7 osurvNRI包1 B$ f" ^4 k# z7 a
logistic的NRI7 k n+ G& k4 \; m* ~0 C: `
nricens包 ( I0 T( p( Q/ Z: }, v( A. L#install.packages("nricens") # 安装R包 * |* Q( Q! B- x3 i! Hlibrary(nricens) ( y+ U& F" v) p* J8 T/ @* r) I12 g1 I3 V1 n j8 I$ t
## Loading required package: survival" E4 L/ r. [# B1 C
1 * ^; s! _- t A使用survival包中的pbc数据集用于演示,这是一份关于原发性硬化性胆管炎的数据,其实是一份用于生存分析的数据,是有时间变量的,但是这里我们用于演示logistic回归,只要不使用time这一列就可以了。- F( D- }( A, o' R( L
1 {" d- H+ _" e, _library(survival) u" k, V! v" p _( S' L $ e7 G2 h Y# p# f; l7 w# 只使用部分数据 , Y4 d5 e2 q' A0 jdat = pbc[1:312,] ( d3 y$ i( ` W# T/ |& A0 Tdat = dat[ dat$time > 2000 | (dat$time < 2000 & dat$status == 2), ] 5 M* @+ @5 W/ j. d- X' T% o5 x! H7 J
str(dat) # 数据长这样 2 d& x/ V: ^2 u1 w1 Y: Y1 " o/ V H7 K% o# P/ ]& e## 'data.frame': 232 obs. of 20 variables: / x, O3 q5 y8 ~) c: h! i## $ id : int 1 2 3 4 6 8 9 10 11 12 ... 4 C1 ^% i3 J" l# t5 M1 t- U; A## $ time : int 400 4500 1012 1925 2503 2466 2400 51 3762 304 ...' L+ o% M! |, d9 z0 p; r
## $ status : int 2 0 2 2 2 2 2 2 2 2 ...$ n, S: r' T& O
## $ trt : int 1 1 1 1 2 2 1 2 2 2 ... , c a- a6 o5 i; d& v## $ age : num 58.8 56.4 70.1 54.7 66.3 ...; u: h5 _& ~5 z- Z7 w, C+ @3 D
## $ sex : Factor w/ 2 levels "m","f": 2 2 1 2 2 2 2 2 2 2 ... % h7 t7 S( j, c& `$ G$ O6 L## $ ascites : int 1 0 0 0 0 0 0 1 0 0 ...; L% L# M8 P8 q" B( O; z4 r
## $ hepato : int 1 1 0 1 1 0 0 0 1 0 ..." z, ^+ S1 F' q$ j' V4 w. A$ S$ D
## $ spiders : int 1 1 0 1 0 0 1 1 1 1 ... 9 @; C* x; o6 l6 N- I" H6 J+ _% w2 s## $ edema : num 1 0 0.5 0.5 0 0 0 1 0 0 ... 9 v' d/ D7 S' c## $ bili : num 14.5 1.1 1.4 1.8 0.8 0.3 3.2 12.6 1.4 3.6 ... ) D0 p8 d; _9 @% t' ~# z( H## $ chol : int 261 302 176 244 248 280 562 200 259 236 ...: z) o7 G9 K) h2 c. g) k# A
## $ albumin : num 2.6 4.14 3.48 2.54 3.98 4 3.08 2.74 4.16 3.52 ... 0 v6 t4 W0 T7 e' J* G# b## $ copper : int 156 54 210 64 50 52 79 140 46 94 ... D. |4 @/ Y: Z$ L## $ alk.phos: num 1718 7395 516 6122 944 ...- v# i j2 s. U, u& D: Z
## $ ast : num 137.9 113.5 96.1 60.6 93 ...& w! N2 B+ R: m% f+ b" D5 `2 W
## $ trig : int 172 88 55 92 63 189 88 143 79 95 ... : I( w8 h: p8 g1 |# H; w j- V% H## $ platelet: int 190 221 151 183 NA 373 251 302 258 71 ...' C- { y! H" [ `8 {
## $ protime : num 12.2 10.6 12 10.3 11 11 11 11.5 12 13.6 ... B0 t' @$ ]8 G
## $ stage : int 4 3 4 4 3 3 2 4 4 4 .... x' u3 J7 \5 R ]8 i
2 f9 M) O8 D+ u4 Y) i! E+ ~5 l' N
1 / O' n }8 V5 ~ g9 W1 xdim(dat) # 232 20 # J5 f' N* @, e2 s1 % e5 e) p5 f K8 a [ x% W4 X## [1] 232 20 * ?6 @* k4 B5 D1; y; ~' h$ S' B8 f
然后就是准备计算NRI所需要的各个参数。 + x1 H' n8 t7 Q& z7 I/ r/ t2 n, _+ ]& S; d
# 定义结局事件,0是存活,1是死亡( Q0 f: ]: ?, L' H4 n: ~! I }0 x# R# n
event = ifelse(dat$time < 2000 & dat$status == 2, 1, 0) & Q& U, o0 Y6 X: P! f5 l1 J* [, I) R9 a( e
# 两个只由预测变量组成的矩阵1 O" i* z @4 W2 F
z.std = as.matrix(subset(dat, select = c(age, bili, albumin))) 8 ?0 i1 ?8 Q, [8 o H$ F- sz.new = as.matrix(subset(dat, select = c(age, bili, albumin, protime))) / ~3 _1 |7 w% j+ o ; W6 u' Z8 Z) @( }# 建立2个模型, f( F1 n/ d, {9 I5 s/ v, h
mstd = glm(event ~ age + bili + albumin, family = binomial(), data = dat, x=TRUE) $ F% R- k- E+ p) p/ W% \mnew = glm(event ~ age + bili + albumin + protime, family = binomial(), data = dat, x=TRUE) 6 A2 K4 J3 o. s d! v& Q+ p3 L & x4 X7 ^- s6 M( H8 i+ O8 _2 [# 取出模型预测概率 |1 O: T% A' D. N. l; S" d6 Q
p.std = mstd$fitted.values: |, p2 P0 l1 |0 |
p.new = mnew$fitted.values4 }! g- k( l; U$ i# Y' z. q* g: Z4 s
' p- m% R* L+ ] T. a4 z: x" H/ z# y
1( B% Q- f. P4 H3 a- J6 t' X- ^
然后就是计算NRI,对于二分类变量,使用nribin()函数,这个函数提供了3种参数使用组合,任选一种都可以计算出来(结果一样),以下3组参数任选1组即可。 mdl.std, mdl.new 或者 event, z.std, z.new 或者 event, p.std, p.new。 5 h6 Q- y; t% d( P0 V& M 1 ]8 l1 l; j/ z# _# v3 R& T0 S' o! |# 这3种方法算出来都是一样的结果 - Z3 u" U& T: q! ~4 o6 U: [; ~$ O1 } g% _4 u3 P; [
# 两个模型0 J4 d0 m9 K2 Z$ J4 D9 |/ I
nribin(mdl.std = mstd, mdl.new = mnew, + f0 D/ y& c; b# e1 T' F. o
cut = c(0.3,0.7), 1 P) U/ C; z+ m1 j niter = 500, 2 W" O" ^9 d! n
updown = 'category') , z1 r0 v$ X5 W # ~& i$ O% U6 C, v, r& j# I# ~# 结果变量 + 两个只有预测变量的矩阵2 C9 k$ g2 h( @9 L
nribin(event = event, z.std = z.std, z.new = z.new, ( z( `( K7 C1 d5 r6 }: n) b
cut = c(0.3,0.7), . l2 W/ K" ~; V1 N. V6 ?
niter = 500, , H& F% o) ~- N" t, |
updown = 'category') " @# w+ M% g: `/ B$ {( @& l6 I3 x$ `
## 结果变量 + 两个模型得到的预测概率 ' [; o8 k: K( T& N% f0 Xnribin(event = event, p.std = p.std, p.new = p.new, ! n8 i7 B! X1 I) S
cut = c(0.3,0.7), 1 R0 H6 k {$ R! T) c) |* ?
niter = 500, ; q* W0 Z; ^, r1 K( B3 [ updown = 'category') 5 \6 ]( j9 t, g7 V0 B, q* r" K5 ~+ L4 T0 K, w3 x* X) @+ Q
1 " O) y( I, k4 F1 ]% n' j其中,cut是判断风险高低的阈值,我们使用了0.3,0.7,代表0-0.3是低风险,0.3-0.7是中风险,0.7-1是高风险,这个阈值是自己设置的,大家根据经验或者文献设置即可。 2 ]! [8 m" m8 c: ^ y# d: v y8 D' S: v5 u6 `! w: s, _& e
niter是使用bootstrap法进行重抽样的次数,默认是1000,大家可以自己设置。/ L' j. ]! ~, ?# H
+ f$ p Z" w! u. t% `; z0 {updown参数,当设置为category时,表示低、中、高风险这种方式;当设置为diff时,此时cut的取值只能设置1个,比如设置0.2,即表示当新模型预测的风险和旧模型相差20%时,认为是重新分类。/ L* G! W# p" ^+ }! \5 s' D
' v! S' m% n6 }, f& T( M) B1 O, l( X/ f# C上面的代码运行后结果是这样的:/ r* n/ x% o; \( M& i- Q5 a8 E
3 T/ a4 ]5 R! B* S6 X( [/ q
UP and DOWN calculation:& S9 G$ o' h# X5 D4 g0 H8 p
#of total, case, and control subjects at t0: 232 88 144 ( m% i8 k7 b4 q: n( n- J" F! ? 1 A. {6 m0 W* H' Y( c0 k' ~ Reclassification Table for all subjects:3 H% u; c d; z
New / O! E; {7 O+ H" |0 a% V UStandard < 0.3 < 0.7 >= 0.7 , ?3 t$ F% X* r8 ?8 _ < 0.3 135 4 0 . _0 n5 w- _+ r7 R, L, P# f( ` < 0.7 1 31 40 B; H1 g9 e: i f' v6 i
>= 0.7 0 2 55 3 @6 \) L, s" o- E* n0 U( _% a0 _3 ` K; j9 ^
Reclassification Table for case: * {# x" ?" n+ K' @9 y New1 Q8 h5 b- _5 c* c- K9 C
Standard < 0.3 < 0.7 >= 0.7 6 Z; @6 w/ c, A2 r5 n < 0.3 14 0 0 6 T/ n& A3 b( l$ m$ M- }. M < 0.7 0 18 35 s* `" L8 o6 e. u. H
>= 0.7 0 1 52: k) W% a, r! p8 R+ f1 d% m
: l; P, Z6 ^) i Reclassification Table for control:+ R$ P* z- a! u" [, |5 r/ Z
New $ S3 t; i% D4 R5 y7 y- A# \% qStandard < 0.3 < 0.7 >= 0.76 Q" B0 L3 {$ L" j
< 0.3 121 4 03 Z- k6 b* m9 C2 ]. [
< 0.7 1 13 1: ^' ]7 e3 C$ G3 h
>= 0.7 0 1 3% ?0 g1 I3 N6 q- `7 H
+ H( |$ E( `$ l4 W+ L* X
NRI estimation: I2 ]% k |/ ZPoint estimates:- x# M, g( ^8 k% T: I
Estimate4 R, M; o! T# q6 C
NRI 0.001893939. V) p$ [ S# `; e* i A1 @2 s4 h
NRI+ 0.022727273: Y2 U; O6 g) a
NRI- -0.020833333 . U/ K2 J7 s. s8 i9 O1 ZPr(Up|Case) 0.034090909 8 J0 q2 @( V6 w1 F6 UPr(Down|Case) 0.011363636 " q6 |$ `: n" n6 J& ~. B6 P% YPr(Down|Ctrl) 0.013888889 $ d: L" \$ M$ v( K3 y! I# lPr(Up|Ctrl) 0.034722222 ! k! O8 Z1 c) {% E ; M3 a5 L4 H+ tNow in bootstrap..0 r# }4 M% _, @4 V( S3 e
+ Q# A- K+ Q% O; g( O
Point & Interval estimates:- ^+ @4 @# ?1 j2 c \, r
Estimate Std.Error Lower Upper! }$ `: l2 W+ O4 a
NRI 0.001893939 0.027816095 -0.053995513 0.055354449 7 z B1 l1 \& D0 X: a$ n @NRI+ 0.022727273 0.021564394 -0.019801980 0.065789474+ G# J, K% I3 T: N
NRI- -0.020833333 0.017312438 -0.058823529 0.007518797, R0 }5 X$ v4 S7 m( j1 ^* @
Pr(Up|Case) 0.034090909 0.019007629 0.000000000 0.0721649481 ^2 k! x* r) O
Pr(Down|Case) 0.011363636 0.010924271 0.000000000 0.039603960' F+ ^$ b2 @2 ^
Pr(Down|Ctrl) 0.013888889 0.009334685 0.000000000 0.035211268- M! M/ C9 \1 [( L `' x: B
Pr(Up|Ctrl) 0.034722222 0.014716046 0.006993007 0.066176471 4 D! _, a' w* @ % B. Z& v9 w5 U: v0 ], @ {, J1 & q$ w) Y1 J ^3 G7 M) c3 T首先是3个混淆矩阵,第一个是全体的,第2个是case(结局为1)组的,第3个是control(结局为2)组的,有了这3个矩阵,我们可以自己计算净重分类指数。 + s) r0 D2 @- Z% k0 ] , w+ V1 V9 F5 g' s3 o/ b看case组:" D( Q2 C# J3 _3 d9 A4 ]8 B
5 ]* Z7 i/ w% L# x9 k
净重分类指数 = ((0+3)-(0+1)) / 88 ≈ 0.022727273( Z' f0 `' Z/ a; F5 e
" ]1 n8 u0 n! I6 _; _
再看control组:7 Z% V( i3 P: c4 G
) R0 _, n8 {' h
净重分类指数 = ((1+1)-(4+1)) / 144 ≈ -0.020833333 , H; H& L( o* T/ @" W d1 h" Z3 P C' V. L7 w% C: V
相加净重分类指数 = case组净重分类指数 + control组净重分类指数 = 2/88 - 3/144 ≈ 0.000315657; h4 x: |4 h( G: v0 w
" J# k+ x$ Q) K, o2 o% G; [7 }再往下是不做bootstrap时得到的估计值,其中NRI就是绝对净重分类指数,NRI+是case组的净重分类指数,NRI-是control组的净重分类指数(和我们计算的一样哦),最后是做了500次bootstrap后得到的估计值,并且有标准误和可信区间。 $ L7 X' A1 _% p$ K) t # T' }" g+ u: E. j" t最后还会得到一张图: . @ m* n" @2 @7 a' y p' { ^3 n6 A! `1 V: Q
这张图中的虚线对应的坐标,就是我们在cut中设置的阈值,这张图对应的是上面结果中的第一个混淆矩阵,反应的是总体的情况,case是结果为1的组,也就是发生结局的组,control是结果为0的组,也就是未发生结局的组。 6 B& _. _* i% K9 ?* n6 E; @8 _* D6 {; x/ i$ e: v; H0 b/ h
P值没有直接给出,但是可以自己计算。1 M! h# ?0 {6 z
* C: L8 v, S' N- |( h
# 计算P值 $ h! c- d4 b$ w: X! L1 \3 Rz <- abs(0.001893939/0.027816095) 0 h& Q7 Y$ [( ^1 ?) kp <- (1 - pnorm(z))*24 t( L0 c) {& r' m- G5 d* F
p 7 N6 v, T* d2 U7 B) H17 R. U" B" ^. s1 Y9 k: p3 ~% y
## [1] 0.9457157, O/ k. C: {8 h2 v) e
1" F4 ^! Z3 ~+ x7 I
PredictABEL包 W, `4 n% n2 _ I: G: P$ Q& V#install.packages("PredictABEL") #安装R包 - _+ d8 n' o0 e, D+ D6 Ylibrary(PredictABEL) + M2 p, M( j' t5 w& `3 H9 [$ e) a
# 取出模型预测概率,这个包只能用预测概率计算! L7 G q$ D' s; }
p.std = mstd$fitted.values, }7 m P- b: X' |/ q' A$ J
p.new = mnew$fitted.values . o4 u0 l; M% }5 r+ X
1( o- C5 P! b# N9 p4 o
然后就是计算NRI:7 ?: Q0 I, m$ g% c7 ~, e- c- w) S